From 27ff585924b9c74a3138c9d7468921d44409134f Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Sat, 2 Nov 2024 09:24:00 +0800 Subject: [PATCH 01/24] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=AD=A6=E4=B9=A0UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- package-lock.json | 45 +++ package.json | 5 + react-ui/config/routes.ts | 21 ++ react-ui/src/app.tsx | 1 + react-ui/src/assets/img/editor-parameter.png | Bin 1264 -> 1848 bytes react-ui/src/assets/img/mirror-basic.png | Bin 468 -> 1470 bytes react-ui/src/assets/img/model-deployment.png | Bin 1595 -> 1689 bytes .../src/assets/img/search-config-icon.png | Bin 0 -> 1567 bytes react-ui/src/assets/img/trial-config-icon.png | Bin 0 -> 1692 bytes react-ui/src/components/BasicInfo/index.tsx | 18 +- react-ui/src/components/KFIcon/index.tsx | 4 +- react-ui/src/iconfont/iconfont.js | 2 +- react-ui/src/pages/AutoML/Create/index.less | 47 +++ react-ui/src/pages/AutoML/Create/index.tsx | 220 +++++++++++++ react-ui/src/pages/AutoML/Info/index.less | 40 +++ react-ui/src/pages/AutoML/Info/index.tsx | 61 ++++ react-ui/src/pages/AutoML/List/index.less | 20 ++ react-ui/src/pages/AutoML/List/index.tsx | 311 ++++++++++++++++++ .../AutoML/components/AutoMLBasic/index.less | 7 + .../AutoML/components/AutoMLBasic/index.tsx | 81 +++++ .../AutoML/components/AutoMLTable/index.less | 15 + .../AutoML/components/AutoMLTable/index.tsx | 305 +++++++++++++++++ .../AutoML/components/ConfigInfo/index.less | 42 +++ .../AutoML/components/ConfigInfo/index.tsx | 47 +++ .../AutoML/components/ConfigTitle/index.less | 38 +++ .../AutoML/components/ConfigTitle/index.tsx | 22 ++ .../AutoML/components/CopyingText/index.less | 18 + .../AutoML/components/CopyingText/index.tsx | 33 ++ .../components/CreateForm/BasicConfig.tsx | 85 +++++ .../components/CreateForm/ExecuteConfig.tsx | 134 ++++++++ .../CreateForm/ExecuteConfigDLC.tsx | 258 +++++++++++++++ .../components/CreateForm/ExecuteConfigMC.tsx | 82 +++++ .../components/CreateForm/SearchConfig.tsx | 119 +++++++ .../components/CreateForm/TrialConfig.tsx | 193 +++++++++++ .../AutoML/components/CreateForm/index.less | 130 ++++++++ .../components/ExecuteScheduleCell/index.less | 30 ++ .../components/ExecuteScheduleCell/index.tsx | 25 ++ .../components/RunStatusCell/index.less | 19 ++ .../AutoML/components/RunStatusCell/index.tsx | 44 +++ .../AutoML/components/StatusChart/index.less | 8 + .../AutoML/components/StatusChart/index.tsx | 217 ++++++++++++ react-ui/src/pages/AutoML/types.ts | 6 + react-ui/src/utils/clipboard.js | 12 + 44 files changed, 2763 insertions(+), 7 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 react-ui/src/assets/img/search-config-icon.png create mode 100644 react-ui/src/assets/img/trial-config-icon.png create mode 100644 react-ui/src/pages/AutoML/Create/index.less create mode 100644 react-ui/src/pages/AutoML/Create/index.tsx create mode 100644 react-ui/src/pages/AutoML/Info/index.less create mode 100644 react-ui/src/pages/AutoML/Info/index.tsx create mode 100644 react-ui/src/pages/AutoML/List/index.less create mode 100644 react-ui/src/pages/AutoML/List/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/AutoMLBasic/index.less create mode 100644 react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/AutoMLTable/index.less create mode 100644 react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/ConfigInfo/index.less create mode 100644 react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/ConfigTitle/index.less create mode 100644 react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/CopyingText/index.less create mode 100644 react-ui/src/pages/AutoML/components/CopyingText/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/index.less create mode 100644 react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less create mode 100644 react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/RunStatusCell/index.less create mode 100644 react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx create mode 100644 react-ui/src/pages/AutoML/components/StatusChart/index.less create mode 100644 react-ui/src/pages/AutoML/components/StatusChart/index.tsx create mode 100644 react-ui/src/pages/AutoML/types.ts create mode 100644 react-ui/src/utils/clipboard.js diff --git a/.gitignore b/.gitignore index 0203dc65..04212571 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,6 @@ Thumbs.db mvnw.cmd mvnw -# Files or folders need to be retained -# ... \ No newline at end of file +# web +**/node_modules + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..d579dd5a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,45 @@ +{ + "name": "ci4sManagement-cloud", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "clipboard": "~2.0.11" + } + }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "dependencies": { + "delegate": "^3.1.2" + } + }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..32560620 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "clipboard": "~2.0.11" + } +} diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 66de518b..1cdbd027 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -135,6 +135,27 @@ export default [ }, ], }, + { + name: '自动机器学习', + path: 'automl', + routes: [ + { + name: '自动机器学习', + path: '', + component: './AutoML/List/index', + }, + { + name: '自动机器学习详情', + path: 'info/:id', + component: './AutoML/Info/index', + }, + { + name: '创建自动机器学习', + path: 'create', + component: './AutoML/Create/index', + }, + ], + }, ], }, { diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 4f911013..e10540ea 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -21,6 +21,7 @@ import './styles/menu.less'; export { requestConfig as request } from './requestConfig'; // const isDev = process.env.NODE_ENV === 'development'; import { type GlobalInitialState } from '@/types'; +import '@/utils/clipboard'; import { menuItemRender } from '@/utils/menuRender'; import ErrorBoundary from './components/ErrorBoundary'; import { gotoLoginPage } from './utils/ui'; diff --git a/react-ui/src/assets/img/editor-parameter.png b/react-ui/src/assets/img/editor-parameter.png index b5fd9f41a4a8abcc2174182549f408e41f6d3995..06fb4f2bc1237804de27b35cf63b2dc4a9e3cb25 100644 GIT binary patch literal 1848 zcmV-82gmq{P)Nklo80vEzI*OHzkB}QmVsW|#$BE=sE}rX%g&&j0hDuq;S7giISnJpQ5b?x z*oZE@`4j~&ZP#L4SMZOQ< zydyeuqd2}E`=H)h6TsBG$USljd?*QLk(&cbDkV7~7Jr{5i>1+-=g0AL25XRX2yot( z14BTP7hc`MN8=tdOpW$@5rb5J+6--$CWxN|h}vcB^qr(Z%#+$FtqC8r(&@pm*ljSV zT5z)bdyq%!OJHU?Nf#4yss+RNyv|^QkN_hsY}zPa?L0G`+?2SM)|N?1YU$)%o}pA+ z)s=AH^^cdnGM@bX=@OZkE^;z-l7`a$zz1J}#S{2$FY`Rd=ao5O;dF5^nu4pzNiz7#0<;H7 z0($RRRV!ak0nx{ffz&Gb<>RM; zXDV{7K#egBEsJ3{y0Cxp18qA==1lZ3w ztOOfG;vX?84GdecU}&KrcB&<`^@d?;ZpejdLd`DT0341WV>zi~SrHzSp&9EW-D@UV zVk%&0K6yKTR=w!7sK=(ltkK+>79M2`C8BAZLnz+Cb`+wYDi|YPOuxtM-8;*U7k~`N z5dkuPlrspSsq0NMmefmEvjnHF&lfm}u@2sNV;#UTO35TV}q zZV&+rtw8lVg@s^PIYG<7)U@(2Za(QQ3>l1|eKe_0pX`W@LnnezLWEiaUhi*bK{GaE zT8-3%^IAD=p1TJ_02J`T92t%uW+A;zvXTs->iN{bP#ss1$FoE-IjL~yNoSxTpAD3) zdSj8~n!Ppo&7MQ~zzJ#bT=pTPV-1+B$3lrjIq(6f{UXAydGUMJ*HU6zaQ(Eps-X?{(C!t)*ui6TG<}`QT0W z@)!2g^Q|A=+jr~RJ(OC3ihhzeBmJb)D|9tlA{HST!HAj}3XT|40yI{+;Y8TEWSel; ziWDmR$4|QMeO7?Y2S!alvC;$)eG3+7CJ|AP>r`kdiNp&Ozda_u3(O&t)nQrHTjZ3wM zHuBTqj-d-Jt;#sX9A>-y`zOYy9TcCe0n@@`6!a+CqignS87D^|Rj+(XjLX9#>4E40 z#a5KLBA1ikM)tdft~skBlxLx9ZpNQMqDJYx8ieG4m6+ZH&_xJavs5x_6;YTZV8~pX zURErMHB5o9Pmm;~LYT|>=rAl%GXj8df>QfQaZO~!HdwE&F15K_PAxGV;x=ef=2`}Z z7!v8d4g6@xK0IMMdEu4`&{#$|hQX>9-h?-;Csh(Wh^i?(qdNV!%$sQqUiq$;0Of56 zt`mXnr8Dys!z_2rRaCs4zR)UzctzPhLfkv)cRxNqN;tvG}JVQ+xfg$0XkTYy4CA zeh9|Taw`GZ`P8FIJ~?D(fx^;1NzyVaHhh_lVOSa#Fj!zl_8H7XEpto>LO5um^*35w z+;3y@dR)FSfbiHL>_U}EQo?;$?#M;@4$I87PP?lV?#wSzK9Q~A@5TQk1{2*zD84p8 zP2m}_jE{=#gR+w(wb%ur`w3b~oD;J)#xVUH3Txv~`3!*Ne&Gp9pQ@5#W74vVRwONT zUR<>?hVz8;qQ~&PX)t2VkJ8@i0-{kNH7@=)*#^KE5L;|LpUn=94bn<3da}Blw0pe~ zxw%O<|8qUiTh@iFwGxsd1&9m-jzI%md%={Sm&=5VYg1bpnJAkg5KqBxqORPN5(Yp8S zw&!O))(^c)${R$*lXhmU!zXP5@nUe$G68?HD)Pb94~xNA66vihfH_i<{O)HM?T4;6*$Pj$q)BPmHRtnS$ zF=U1-+D1TXtx@xT`^z#Y9CSfSVkRR(&@qXyu4+1%D)*6^Q`&cEJ1PS>xQ<9cUhL9` zh0|Er$1JoTm7_Ws304L23=FLnXoNvEl#%TN((fSRTN&2Cg-Fr_77AHI!7P!3f>^jz zrSO>#i~fcJG4lb}%Kl_na8Bv1@ZvmXpa!O*_>F@Z+|8yFb| zBcI43y%aw$#(C;(&&@2Wp1m%9hfvDpYhN7C{yM}(K^bXj-S5_te@y2S(}CQd@A8$AHa%InBoQ7i-KlCSY6x8im>UKYX=}39 z8@cxW{jdQg1)FJ^_v331f-X<0x1{NVfL*RJF|aNf$$<+9bh8F;V4QPwJNSP auKxkknaVEgf$&QJ0000%O>7%g5T3WOLr{V!qJ|=;vXLMs1mVC11vL>8^nyqa{2wqh z6;g#nICB7Qa3U8jty7#7aREefpb|o&iMVlTIRI6unoVP;`GpNm9LGQV_}(V#>~8kQ zyIwcO>?O*2=FNO>zL_`UXGU%Gur|U%{~s{Nk0)Ox;?HqT2_n`}iz1UGPOP>Oj3B{P zwRutIRal#cmG86J`EQ-p8-=|7w-|B z8ANBOE-=Uyt_$uA%sj1jCrUvas;E>^@^qcPX4^sZjXJSdtf#B1JHHEtZz6Z@sRv91 zV9L8ntXMpGbD$ANuq%c2H2b5GHFRC%aRJe}Tl~MUAV=`0YFyWjW}$)+#&$|1=O@{E zqkvih3~KiBUul+Y3otmE^K0kX^|k<`6@m-w*Omj*aqW}7$l6aQ{5r-q|Ge!-2jzD< zmy@p=bwbaXt{)zmVN~t$<5)mh1r_VQb^)^iFh`mShUO+8x+{0_1wXsZ)lyk1+*jp9X-Srw3&xK}d$MqBUCA($V*!?-&~qk;6C}~%+BxQ{0NB|H0*3V73^U7i@%fw2 z1Ol)hm8!BuzoS13)ZZCx!>_;bzm`YHrvu%KZPy$onBopCY+ht5jckani^#l--s}U6 zfuEvmk6G9B=*?)mcycayWI{Um>)i+sFuag|R(?Dh-3_gur(7mwqd+p~W$JAGtl?MI z6Ab2t?jtEKz$7CP+W#KF!4fK$a9&3BQv5n&lovLm9rPyN^+MBk&I+Vvi<4Zc(vx<8f`G|jVG;gN9z6TOd>JIkHXn| z_vAiaNwbfv4)_KW3Ic~k1icuiEBeP6>W0?ROpDmj5k4ppUETvZyP9UT4bbIV3(N>d zM^4&!fru&S9EMiUGi?k2{gRy#n)^?Hx&JgD?qKwtHD~~pDOc%zRJO4i{Wwjmjlc*xNjXlU=(u5hp!*F0t#f?x-96LIZ=l8_~tuRui`{fo5Ym&20KF zQ!aSXFElWIc;zH(&4rb5W;$Qo=;vnNH_aLaqgAK~iCJEIs+>-(kF%*7y*%jGQeZ%# zQ8bL7?d8gWQj9al*#5hgVk@va9vKtGh)m9U(a(z&Ed$2d{#)ti1w?%A^i`9%gX|Il zrP6lK^78UlJ&V;NW9*Jml4ntpQ_%>$hx(kVwKRBdH?MfD?*Jz8GjQs6nLpw}6dVtJ z$;x^8MlP4rZqAHziJf!Wbcse&pJTiL7(aK4%xe((%FjY>ve=BAs+Ri}7Z(dc4y9Ky zjKKS3x*K&=_;k)P0gSG?Y02jLN3(9sa z-It-8HZ7VT2>gM-c0t8VCe0W0O zgiOLsbIzW zADh#K6uf>aDyo_Wa|RfDx!Tg&kCJT?6uivd}mihD$tjk(n;$ZH4XMX9M*ZP?G<@z}W=2 zXpMSK=%!j1(SH)E-LX;$7Zu^z9f50z!$Yxs&tWtu)oS=Ci0>t3EDRl85DJ7c1tyf zbAI>0ak+~Nfd~H2u*C74kP>rpq4QjAHeHL2v4=p7rB0;0bOMjJR$P&{)?@O25I}<Gh{3D>?nZZGm}mEh@_U3OTLer; ziuhj4b1R{gLhs>s_PJ8swE^>^6}qPxG`U*MC8rAzU45Er$bxCH}ID;96*9Rp+EiNdt8fMWF*R?Zy-X!{o7e?x@mu_vwMZ zsOfve(!j7yVNK0pTqSk2_#fh4u*p)u5CEvT9N~6oY}w@pD3@9jkm_nmDo1q`k? z5If)X6*gAGahu|iD2akx1%MTJ=aUn@Ts9ci>snCMW>r+E6HXC=R}ppDz*}P_V4MJe z!X4nI1!Rr<8TFkjk3vPP6ig@>t^xy+r*jwD&fh78{OVz>Hldz({H~%e3Z_o$i}o)S z%yUUIT2!J1<}Vct?mz|brHafDH~UjT&t04KG})CiZgHyUR|{x(-l?2D&S7h1rpg&7 z2n!CxB+UOk#lRpq+1aN;4Hrt?J%3?v-|m&YwKAh7+bZ+Vxk^HfS-dQ>%X00`Lf2Q> z`Q<3DEPi8Pc!%4K#4gl^g0mR_eNKU6E!=fVkj#j<)nJ{ODo&u-4U0_Bdvx zIxueWu70o?RRDseEEWUGRRR|^)ajR<2kQcoo1~k`y`H2rZO3;vHnMJhE4G#tpw|~i zc|Y8&DVT5`(FRZuAbmo}!oC>m2~TKqw;blVIVahzZj*l>=EHFDRC{x;97|wRQhmBM z)^YooDKRjQJz<*e(`ei`yXE$JKDid3yg$sbbYn6B<8ul*(ENcvIR*xoX#HiRP9ogv ztM%-ytkTgzdH3li+p3FBu{E7_VrM6nXGU6x4+@o`unvIV|7fp7NhajWy`IXw#^HQx zITLJdlr%pa<}>-N$~sj^6Mv*mo7uDt2*w6sc*Z?Ubi0-N*`VOoClp^@XFH(O97yJaCDM97ny@~+5T%d>qBJAgEYI&DI} zb;{f9`Ni+KnG*xUL^@;P7gqrsNsDg9OaYnj^LBX zR%XTfqnw}>1>*n?)K9{UCNED>fb*$P11p;MI{4iO8+RWVV`s&M>X^V@^qX8lIp;O687{0CSi8P zRb>ue@=Xop2Y}Ez5_n&^21^|63)PN_HvX03= z8(Cok#88fJ0*z`nJYhiXrij+{&zi3szqE;gA=K3#>=dfhd*zvd`pmbu&^iPOS%y+& z>U;v^w#@qe;pYr0`Mx3fhCm${T*e$yQR}boO@IX!my;8F(RM;U7&_cPXAB^&W2K^Q zlcxQC1WJ|hskNp;ahaX#PSXpm=7V7!tPy^WRPI^mVl jqF`1kT-(L(6_|ekMV4+-6aEg!00000NkvXXu0mjf35Op+ literal 1595 zcmV-B2E_S^P)0PiSmPH}REDca2S)%oX(hH0DnlrVI&2GvD!SaDcxl;fB+80qaKg} zIqD+N#u@-D{ZClH`iNO9g!1bo)&j&-{GAibI{V^I1~LFIo$_zg#tT^dwrLWKWF7&; zPhA!T07Cu_lb(n7E;5FW#UmJt3*&J?FFT+df7Fzy~o_Sco6= zRA3#`Ob_z?sglx3}XU;qvVz#>=&AQQ2#mP_>Q)P<6>z%~&OYXji5!HT#XOTs?H z+G>r$cnu~TQ=xck08&Z<3WfmX<9VTX81n5^V81AURm$|~9}EDu?gcmZQDEqt!2nR; zYl2{(Sq215D~gX2HfgHHpqo;w)z>hn34m?DnHp4MHse}pds2)xx9TbS{j;2G)rmX+ zEDllPV

{19R-=y*KR~6vug&?-$_nbcA_RP#A$S2 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 RIl}+|002ovPDHLkV1hqb0MGyc literal 0 HcmV?d00001 diff --git a/react-ui/src/assets/img/trial-config-icon.png b/react-ui/src/assets/img/trial-config-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..69408563ddae054c4aee600475948b043d8c0999 GIT binary patch literal 1692 zcmV;N24ne&P)9(E6h3#}yy+jbrGNGphG;0bv9&9rks)yjp)6QPf{eCc zVoa#S1um3nqS1|^Y)o2;v=ZaOC@o=Qx}dlaV&V@P(n3V0Ka|=|+o{uHr_*`YbDw?V z?YzJH=9Q@-yyPXF_s%`{`_4J{+;i?Lfyd|~j}aGmLZXZxrQ&UzVZR6je%-;;H-h!@p#clqs2# zR$A);c*+b6^MpLw-AQP2v!M(j^FV2EY%JWDEkcH5*aD}^P;UhyZ=n7%D9hD0K%}_T6tu%FMe0R#)*UQsGbWmv8dmA!l%R$P=I3_b&l5KQ?wxU3WD#{suze z0^SWS$wd_f+Hp;G@OpHfsjKs2mKDF0f@iwoFAj9^Bjn%SY4T2d-Kjl8d%*7`$8 zB;`@@S&St$z1mwHnc8jjLqv)16rG6X)qT12(s!S%A@lu#!`|4hC)3HGXM!FNToBc5 zB}XaT93Ky_@vx3eS@ZRP?~k(qvK*;Wl@<8!DBhwNuULv)JxrEo_poP_NU_A$);E$n zPu*d^rO}Jk@^JeXT9%`p+}qn!wZZd8q#?mzI zJ0~#J`d8(m_;(-f?7f*pg=Gs+S_C^pGMjE<01reD`=F7+PoXH`~nv=qgVN>^vTZG&ng75lYP>0G-O<+UdbDN6pp)dMmKLo2FBQ4&_-J%|c7t zTUaZMDh3x8Ae7!kim2W&&xn9?D)WSlQ;TIweqB6Mc8u=vPzW&4?Bpbog#Ks`S#xrd z4MMKSl(x`jY)8*vk?H5bA|Xa3G<}}10y1k?#$?K0r$J(KpvS6VB($#8gq3A7BEvpF z`Mkgi(*G_BBY3VG#WedI{}?;Yy^(G*m<{(Bf6_Qc8xWXju}LU2OATiBXG$r5jD}^<~rR?_VT*)WLgoPv#TA!GVrO3c?VnVtJ(vNOnD0xsmX+A*Ys)w zbYyVC9<-xb&7_|QPgADOlY{&V9yD5J9xbT$GUXV)XwpG01cia83!=$;F}VaSJtLwW zE#HQg=MN-8Xr*=)a0@-bdJRpF(WD}CWFnJsHj};Be+YD>ltAP3Ig*vH&KZ#r4H-Vm zWnyWkV=^Yg5$ZrC{P7S=H6!?M8f`P@N!C7_Gd!-yXqH?34C2T2QH+Os;cSIfy64t~ mjMH# ); + } else if (React.isValidElement(formatValue)) { + // 这个判断必须在下面的判断之前 + valueComponent = ( + + ); } else if (typeof formatValue === 'object' && formatValue) { valueComponent = ( ); + } else if (React.isValidElement(value)) { + return value; } else { component = {value ?? '--'}; } return (

- + {component}
diff --git a/react-ui/src/components/KFIcon/index.tsx b/react-ui/src/components/KFIcon/index.tsx index e50dabec..d84257a7 100644 --- a/react-ui/src/components/KFIcon/index.tsx +++ b/react-ui/src/components/KFIcon/index.tsx @@ -21,13 +21,13 @@ interface KFIconProps extends IconFontProps { className?: string; } -function KFIcon({ type, font = 15, color = '', style = {}, className }: KFIconProps) { +function KFIcon({ type, font = 15, color = '', style = {}, className, ...rest }: KFIconProps) { const iconStyle = { ...style, fontSize: font, color, }; - return ; + return ; } export default KFIcon; diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index c9da6580..0e40d9d6 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file diff --git a/react-ui/src/pages/AutoML/Create/index.less b/react-ui/src/pages/AutoML/Create/index.less new file mode 100644 index 00000000..ca173288 --- /dev/null +++ b/react-ui/src/pages/AutoML/Create/index.less @@ -0,0 +1,47 @@ +.create-service-version { + height: 100%; + + &__content { + height: calc(100% - 60px); + margin-top: 10px; + padding: 30px 30px 10px; + overflow: auto; + color: @text-color; + font-size: @font-size-content; + background-color: white; + border-radius: 10px; + + &__type { + color: @text-color; + font-size: @font-size-input-lg; + } + + :global { + .ant-input-number { + width: 100%; + } + + .ant-form-item { + margin-bottom: 20px; + } + + .image-url { + margin-top: -15px; + .ant-form-item-label > label::after { + content: ''; + } + } + + .ant-btn.ant-btn-icon-only .anticon { + color: #565658; + font-size: 20px; + } + + .anticon-question-circle { + margin-top: -12px; + color: @text-color-tertiary !important; + font-size: 12px !important; + } + } + } +} diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx new file mode 100644 index 00000000..9ac79d57 --- /dev/null +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -0,0 +1,220 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 创建服务版本 + */ +import PageTitle from '@/components/PageTitle'; +import { type ParameterInputObject } from '@/components/ResourceSelect'; +import { useComputingResource } from '@/hooks/resource'; +import { + createServiceVersionReq, + getServiceInfoReq, + updateServiceVersionReq, +} from '@/services/modelDeployment'; +import { changePropertyName } from '@/utils'; +import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; +import { useNavigate, useParams } from '@umijs/max'; +import { App, Button, Form } from 'antd'; +import { omit, pick } from 'lodash'; +import { useEffect, useState } from 'react'; +import BasicConfig from '../components/CreateForm/BasicConfig'; +import ExecuteConfig from '../components/CreateForm/ExecuteConfig'; +import SearchConfig from '../components/CreateForm/SearchConfig'; +import TrialConfig from '../components/CreateForm/TrialConfig'; +import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; +import styles from './index.less'; + +// 表单数据 +export type FormData = { + service_name: string; // 服务名称 + version: string; // 服务版本 + description: string; // 描述 + model: ParameterInputObject; // 模型 + image: ParameterInputObject; // 镜像 + code_config: ParameterInputObject; // 代码 + resource: string; // 资源规格 + replicas: string; // 副本数量 + mount_path: string; // 模型路径 + env_variables: { key: string; value: string }[]; // 环境变量 +}; + +function CreateAutoML() { + const navigate = useNavigate(); + const [form] = Form.useForm(); + const [resourceStandardList, filterResourceStandard] = useComputingResource(); + const [operationType, setOperationType] = useState(ServiceOperationType.Create); + + const { message } = App.useApp(); + const [serviceInfo, setServiceInfo] = useState(undefined); + const [versionInfo, setVersionInfo] = useState(undefined); + const params = useParams(); + const id = params.id; + + useEffect(() => { + const res: + | (ServiceVersionData & { + operationType: ServiceOperationType; + }) + | undefined = SessionStorage.getItem(SessionStorage.serviceVersionInfoKey, true); + if (res) { + setOperationType(res.operationType); + + setVersionInfo(res); + let model, codeConfig, envVariables; + if (res.model && typeof res.model === 'object') { + model = changePropertyName(res.model, { show_value: 'showValue' }); + // 接口返回是数据没有 value 值,但是 form 需要 value + model.value = model.showValue; + } + if (res.code_config && typeof res.code_config === 'object') { + codeConfig = changePropertyName(res.code_config, { show_value: 'showValue' }); + // 接口返回是数据没有 value 值,但是 form 需要 value + codeConfig.value = codeConfig.showValue; + } + if (res.env_variables && typeof res.env_variables === 'object') { + envVariables = Object.entries(res.env_variables).map(([key, value]) => ({ + key, + value, + })); + } + + const formData = { + ...omit(res, 'model', 'code_config', 'env_variables'), + model: model, + code_config: codeConfig, + env_variables: envVariables, + }; + form.setFieldsValue(formData); + } + return () => { + SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey); + }; + }, []); + + // 获取服务详情 + const getServiceInfo = async () => { + const [res] = await to(getServiceInfoReq(id)); + if (res && res.data) { + setServiceInfo(res.data); + form.setFieldsValue({ + service_name: res.data.service_name, + }); + } + }; + + // 创建版本 + const createServiceVersion = async (formData: FormData) => { + const envList = formData['env_variables'] ?? []; + const image = formData['image']; + const model = formData['model']; + const codeConfig = formData['code_config']; + const envVariables = envList.reduce((acc, cur) => { + acc[cur.key] = cur.value; + return acc; + }, {} as Record); + + // 根据后台要求,修改表单数据 + const object = { + ...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']), + replicas: Number(formData.replicas), + env_variables: envVariables, + image: image.value, + model: changePropertyName( + pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), + { showValue: 'show_value' }, + ), + code_config: changePropertyName(pick(codeConfig, ['code_path', 'branch', 'showValue']), { + showValue: 'show_value', + }), + service_id: serviceInfo?.id, + }; + + const params = + operationType === ServiceOperationType.Create + ? object + : { + id: versionInfo?.id, + rerun: operationType === ServiceOperationType.Restart ? true : false, + deployment_name: versionInfo?.deployment_name, + ...object, + }; + + const request = + operationType === ServiceOperationType.Create + ? createServiceVersionReq + : updateServiceVersionReq; + + const [res] = await to(request(params)); + if (res) { + message.success('操作成功'); + navigate(-1); + } + }; + + // 提交 + const handleSubmit = (values: FormData) => { + console.log('values', values); + }; + + // 取消 + const cancel = () => { + navigate(-1); + }; + + const disabled = operationType !== ServiceOperationType.Create; + let buttonText = '新建'; + let title = '新增服务版本'; + if (operationType === ServiceOperationType.Update) { + title = '更新服务版本'; + buttonText = '更新'; + } else if (operationType === ServiceOperationType.Restart) { + title = '重启服务版本'; + buttonText = '重启'; + } + + return ( +
+ +
+
+
+ + + + + + + + + + +
+
+
+ ); +} + +export default CreateAutoML; diff --git a/react-ui/src/pages/AutoML/Info/index.less b/react-ui/src/pages/AutoML/Info/index.less new file mode 100644 index 00000000..e27756ef --- /dev/null +++ b/react-ui/src/pages/AutoML/Info/index.less @@ -0,0 +1,40 @@ +.auto-ml-info { + position: relative; + height: 100%; + &__tabs { + height: 50px; + padding-left: 25px; + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + } + + &__content { + height: calc(100% - 60px); + margin-top: 10px; + } + + &__tips { + position: absolute; + top: 11px; + left: 256px; + padding: 3px 12px; + color: #565658; + font-size: @font-size-content; + background: .addAlpha(@primary-color, 0.09) []; + border-radius: 4px; + + &::before { + position: absolute; + top: 10px; + left: -6px; + width: 0; + height: 0; + border-top: 4px solid transparent; + border-right: 6px solid .addAlpha(@primary-color, 0.09) []; + border-bottom: 4px solid transparent; + content: ''; + } + } +} diff --git a/react-ui/src/pages/AutoML/Info/index.tsx b/react-ui/src/pages/AutoML/Info/index.tsx new file mode 100644 index 00000000..c167af97 --- /dev/null +++ b/react-ui/src/pages/AutoML/Info/index.tsx @@ -0,0 +1,61 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 自主机器学习详情 + */ +import KFIcon from '@/components/KFIcon'; +import { CommonTabKeys } from '@/enums'; +import { useCacheState } from '@/hooks/pageCacheState'; +import themes from '@/styles/theme.less'; +import { useNavigate } from '@umijs/max'; +import { Tabs } from 'antd'; +import { useState } from 'react'; +import AutoMLBasic from '../components/AutoMLBasic'; +import AutoMLTable from '../components/AutoMLTable'; +import styles from './index.less'; + +export type MirrorData = { + id: number; + name: string; + description: string; + create_time: string; +}; + +function AutoMLInfo() { + const navigate = useNavigate(); + const [cacheState, setCacheState] = useCacheState(); + const [activeTab, setActiveTab] = useState(cacheState?.activeTab ?? CommonTabKeys.Public); + + const tabItems = [ + { + key: CommonTabKeys.Public, + label: '基本信息', + icon: , + }, + { + key: CommonTabKeys.Private, + label: 'Trial列表', + icon: , + }, + ]; + + return ( +
+
+ +
+
+ {activeTab === CommonTabKeys.Public && } + {activeTab === CommonTabKeys.Private && } +
+ {activeTab === CommonTabKeys.Private && ( +
+ + Trial是一次独立的尝试,他会使用某组超参来运行 +
+ )} +
+ ); +} + +export default AutoMLInfo; diff --git a/react-ui/src/pages/AutoML/List/index.less b/react-ui/src/pages/AutoML/List/index.less new file mode 100644 index 00000000..735e3442 --- /dev/null +++ b/react-ui/src/pages/AutoML/List/index.less @@ -0,0 +1,20 @@ +.auto-ml-list { + height: 100%; + &__content { + height: calc(100% - 60px); + margin-top: 10px; + padding: 20px @content-padding 0; + background-color: white; + border-radius: 10px; + + &__filter { + display: flex; + align-items: center; + } + + &__table { + height: calc(100% - 32px - 28px); + margin-top: 28px; + } + } +} diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx new file mode 100644 index 00000000..aa02ce74 --- /dev/null +++ b/react-ui/src/pages/AutoML/List/index.tsx @@ -0,0 +1,311 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 自主机器学习 + */ +import KFIcon from '@/components/KFIcon'; +import PageTitle from '@/components/PageTitle'; +import { useCacheState } from '@/hooks/pageCacheState'; +import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; +import themes from '@/styles/theme.less'; +import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; +import tableCellRender, { TableCellValueType } from '@/utils/table'; +import { modalConfirm } from '@/utils/ui'; +import { useNavigate } from '@umijs/max'; +import { + App, + Button, + ConfigProvider, + Input, + Table, + type TablePaginationConfig, + type TableProps, +} from 'antd'; +import { type SearchProps } from 'antd/es/input'; +import classNames from 'classnames'; +import { useEffect, useState } from 'react'; +import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; +import RunStatusCell from '../components/RunStatusCell'; +import { ServiceData, ServiceOperationType } from '../types'; +import styles from './index.less'; + +function AutoMLList() { + const navigate = useNavigate(); + const { message } = App.useApp(); + const [cacheState, setCacheState] = useCacheState(); + const [searchText, setSearchText] = useState(cacheState?.searchText); + const [inputText, setInputText] = useState(cacheState?.searchText); + const [tableData, setTableData] = useState([]); + const [total, setTotal] = useState(0); + const [pagination, setPagination] = useState( + cacheState?.pagination ?? { + current: 1, + pageSize: 10, + }, + ); + + useEffect(() => { + getServiceList(); + }, [pagination, searchText]); + + // 获取模型部署服务列表 + const getServiceList = async () => { + const params: Record = { + page: pagination.current! - 1, + size: pagination.pageSize, + service_name: searchText, + }; + const [res] = await to(getServiceListReq(params)); + if (res && res.data) { + const { content = [], totalElements = 0 } = res.data; + setTableData(content); + setTotal(totalElements); + } + }; + + // 删除模型部署 + const deleteService = async (record: ServiceData) => { + const [res] = await to(deleteServiceReq(record.id)); + if (res) { + message.success('删除成功'); + // 如果是一页的唯一数据,删除时,请求第一页的数据 + // 否则直接刷新这一页的数据 + // 避免回到第一页 + if (tableData.length > 1) { + setPagination((prev) => ({ + ...prev, + current: 1, + })); + } else { + getServiceList(); + } + } + }; + + // 搜索 + const onSearch: SearchProps['onSearch'] = (value) => { + setSearchText(value); + }; + + // 处理删除 + const handleServiceDelete = (record: ServiceData) => { + modalConfirm({ + title: '删除后,该服务将不可恢复', + content: '是否确认删除?', + onOk: () => { + deleteService(record); + }, + }); + }; + + // 创建、更新服务 + const createService = (type: ServiceOperationType, record?: ServiceData) => { + SessionStorage.setItem( + SessionStorage.serviceInfoKey, + { + ...record, + operationType: type, + }, + true, + ); + + setCacheState({ + pagination, + searchText, + }); + + navigate(`/pipeline/autoML/create`); + }; + + // 查看详情 + const toDetail = (record: ServiceData) => { + setCacheState({ + pagination, + searchText, + }); + + navigate(`/pipeline/autoML/info/${record.id}`); + }; + + // 分页切换 + const handleTableChange: TableProps['onChange'] = ( + pagination, + _filters, + _sorter, + { action }, + ) => { + if (action === 'paginate') { + setPagination(pagination); + } + }; + + const columns: TableProps['columns'] = [ + { + title: '序号', + dataIndex: 'index', + key: 'index', + width: '20%', + render: tableCellRender(false, TableCellValueType.Index, { + page: pagination.current! - 1, + pageSize: pagination.pageSize!, + }), + }, + { + title: '实验名称', + dataIndex: 'service_name', + key: 'service_name', + width: '20%', + render: tableCellRender(false, TableCellValueType.Link, { + onClick: toDetail, + }), + }, + { + title: '实验描述', + dataIndex: 'service_type_name', + key: 'service_type_name', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '状态', + dataIndex: 'run_status', + key: 'run_status', + width: '20%', + render: RunStatusCell, + }, + { + title: '实验实例执行进度', + dataIndex: 'description', + key: 'description', + render: ExecuteScheduleCell, + width: 180, + }, + { + title: '创建时间', + dataIndex: 'update_time', + key: 'update_time', + width: '20%', + render: tableCellRender(true, TableCellValueType.Date), + ellipsis: { showTitle: false }, + }, + { + title: '修改时间', + dataIndex: 'update_time', + key: 'update_time', + width: '20%', + render: tableCellRender(true, TableCellValueType.Date), + ellipsis: { showTitle: false }, + }, + { + title: '操作', + dataIndex: 'operation', + width: 400, + key: 'operation', + render: (_: any, record: ServiceData) => ( +
+ + + + + + + +
+ ), + }, + ]; + + return ( +
+ +
+
+ setInputText(e.target.value)} + style={{ width: 300 }} + value={inputText} + allowClear + /> + +
+
+ `共${total}条`, + }} + onChange={handleTableChange} + rowKey="id" + /> + + + + ); +} + +export default AutoMLList; diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.less b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.less new file mode 100644 index 00000000..80de7592 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.less @@ -0,0 +1,7 @@ +.auto-ml-basic { + height: 100%; + padding: 20px @content-padding; + overflow-y: auto; + background-color: white; + border-radius: 10px; +} diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx new file mode 100644 index 00000000..32968243 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -0,0 +1,81 @@ +import { Flex } from 'antd'; +import { useEffect } from 'react'; +import ConfigInfo, { type BasicInfoData } from '../ConfigInfo'; +import CopyingText from '../CopyingText'; +import StatusChart from '../StatusChart'; +import styles from './index.less'; + +function AutoMLBasic() { + useEffect(() => {}, []); + + const datas: BasicInfoData[] = [ + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: '测试项目名称', + ellipsis: true, + }, + { + label: '项目名称', + value: , + ellipsis: false, + }, + ]; + + return ( +
+ + + + + + + +
+ ); +} + +export default AutoMLBasic; diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.less b/react-ui/src/pages/AutoML/components/AutoMLTable/index.less new file mode 100644 index 00000000..2d7d326f --- /dev/null +++ b/react-ui/src/pages/AutoML/components/AutoMLTable/index.less @@ -0,0 +1,15 @@ +.auto-ml-table { + height: 100%; + padding: 20px @content-padding 0; + background-color: white; + border-radius: 10px; + &__filter { + display: flex; + align-items: center; + } + + &__table { + height: calc(100% - 32px - 28px); + margin-top: 28px; + } +} diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx new file mode 100644 index 00000000..e4e0f00f --- /dev/null +++ b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx @@ -0,0 +1,305 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 自主机器学习 + */ +import KFIcon from '@/components/KFIcon'; +import { useCacheState } from '@/hooks/pageCacheState'; +import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; +import themes from '@/styles/theme.less'; +import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; +import tableCellRender, { TableCellValueType } from '@/utils/table'; +import { modalConfirm } from '@/utils/ui'; +import { useNavigate } from '@umijs/max'; +import { + App, + Button, + ConfigProvider, + Input, + Table, + type TablePaginationConfig, + type TableProps, +} from 'antd'; +import { type SearchProps } from 'antd/es/input'; +import classNames from 'classnames'; +import { useEffect, useState } from 'react'; +// import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; +import { ServiceData, ServiceOperationType } from '@/pages/AutoML/types'; +import RunStatusCell from '../RunStatusCell'; +import styles from './index.less'; + +function AutoMLTable() { + const navigate = useNavigate(); + const { message } = App.useApp(); + const [cacheState, setCacheState] = useCacheState(); + const [searchText, setSearchText] = useState(cacheState?.searchText); + const [inputText, setInputText] = useState(cacheState?.searchText); + const [tableData, setTableData] = useState([]); + const [total, setTotal] = useState(0); + const [pagination, setPagination] = useState( + cacheState?.pagination ?? { + current: 1, + pageSize: 10, + }, + ); + + useEffect(() => { + getServiceList(); + }, [pagination, searchText]); + + // 获取模型部署服务列表 + const getServiceList = async () => { + const params: Record = { + page: pagination.current! - 1, + size: pagination.pageSize, + service_name: searchText, + }; + const [res] = await to(getServiceListReq(params)); + if (res && res.data) { + const { content = [], totalElements = 0 } = res.data; + setTableData(content); + setTotal(totalElements); + } + }; + + // 删除模型部署 + const deleteService = async (record: ServiceData) => { + const [res] = await to(deleteServiceReq(record.id)); + if (res) { + message.success('删除成功'); + // 如果是一页的唯一数据,删除时,请求第一页的数据 + // 否则直接刷新这一页的数据 + // 避免回到第一页 + if (tableData.length > 1) { + setPagination((prev) => ({ + ...prev, + current: 1, + })); + } else { + getServiceList(); + } + } + }; + + // 搜索 + const onSearch: SearchProps['onSearch'] = (value) => { + setSearchText(value); + }; + + // 处理删除 + const handleServiceDelete = (record: ServiceData) => { + modalConfirm({ + title: '删除后,该服务将不可恢复', + content: '是否确认删除?', + onOk: () => { + deleteService(record); + }, + }); + }; + + // 创建、更新服务 + const createService = (type: ServiceOperationType, record?: ServiceData) => { + SessionStorage.setItem( + SessionStorage.serviceInfoKey, + { + ...record, + operationType: type, + }, + true, + ); + + setCacheState({ + pagination, + searchText, + }); + + navigate(`/modelDeployment/createService`); + }; + + // 查看详情 + const toDetail = (record: ServiceData) => { + setCacheState({ + pagination, + searchText, + }); + + navigate(`/modelDeployment/serviceInfo/${record.id}`); + }; + + // 分页切换 + const handleTableChange: TableProps['onChange'] = ( + pagination, + _filters, + _sorter, + { action }, + ) => { + if (action === 'paginate') { + setPagination(pagination); + } + }; + + const columns: TableProps['columns'] = [ + { + title: '序号', + dataIndex: 'index', + key: 'index', + width: '20%', + render: tableCellRender(false, TableCellValueType.Index, { + page: pagination.current! - 1, + pageSize: pagination.pageSize!, + }), + }, + { + title: 'Trial ID', + dataIndex: 'service_name', + key: 'service_name', + width: '20%', + render: tableCellRender(false, TableCellValueType.Link, { + onClick: toDetail, + }), + }, + { + title: '状态', + dataIndex: 'run_status', + key: 'run_status', + width: '20%', + render: RunStatusCell, + }, + { + title: '最终指标', + dataIndex: 'service_type_name', + key: 'service_type_name', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '当前指标', + dataIndex: 'description', + key: 'description', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: 'lr', + dataIndex: 'description', + key: 'description', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: 'batch_size', + dataIndex: 'description', + key: 'description', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '修改时间', + dataIndex: 'update_time', + key: 'update_time', + width: '20%', + render: tableCellRender(true, TableCellValueType.Date), + ellipsis: { showTitle: false }, + }, + { + title: '执行时长', + dataIndex: 'description', + key: 'description', + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + }, + { + title: '操作', + dataIndex: 'operation', + width: 400, + key: 'operation', + render: (_: any, record: ServiceData) => ( +
+ + + + + + +
+ ), + }, + ]; + + return ( +
+
+ setInputText(e.target.value)} + style={{ width: 300 }} + value={inputText} + allowClear + /> + +
+
+
`共${total}条`, + }} + onChange={handleTableChange} + rowKey="id" + /> + + + ); +} + +export default AutoMLTable; diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less new file mode 100644 index 00000000..a51e7ddb --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less @@ -0,0 +1,42 @@ +.config-info { + width: 100%; + border: 1px solid @border-color-base; + border-radius: 4px; + + &__content { + padding: 20px; + padding: 20px @content-padding; + background-color: white; + } + + :global { + .kf-basic-info { + gap: 15px 40px; + width: 100%; + + &__item { + &__label { + font-size: @font-size; + text-align: left; + text-align-last: left; + &::after { + display: none; + } + } + &__value { + font-size: @font-size; + } + } + } + } + + &--three-column { + :global { + .kf-basic-info { + &__item { + width: calc((100% - 80px) / 3); + } + } + } + } +} diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx new file mode 100644 index 00000000..e12b291b --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx @@ -0,0 +1,47 @@ +import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; +import classNames from 'classnames'; +import { useEffect } from 'react'; +import ConfigTitle from '../ConfigTitle'; +import styles from './index.less'; +export { type BasicInfoData }; + +type ConfigInfoProps = { + title: string; + data: BasicInfoData[]; + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; + labelWidth: number; + threeColumn?: boolean; +}; + +function ConfigInfo({ + title, + data, + className, + style, + children, + labelWidth, + threeColumn = false, +}: ConfigInfoProps) { + useEffect(() => {}, []); + + return ( +
+ +
+ + {children} +
+
+ ); +} + +export default ConfigInfo; diff --git a/react-ui/src/pages/AutoML/components/ConfigTitle/index.less b/react-ui/src/pages/AutoML/components/ConfigTitle/index.less new file mode 100644 index 00000000..71e55252 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ConfigTitle/index.less @@ -0,0 +1,38 @@ +.config-title { + width: 100%; + height: 56px; + padding-left: @content-padding; + background: linear-gradient( + 179.03deg, + rgba(199, 223, 255, 0.12) 0%, + rgba(22, 100, 255, 0.04) 100% + ); + border: 1px solid #e8effb; + + &__img { + width: 16px; + height: 16px; + margin-right: 10px; + } + + &__text { + position: relative; + color: @text-color; + font-weight: 500; + font-size: @font-size-title; + + &::after { + position: absolute; + bottom: 6px; + left: 0; + width: 100%; + height: 6px; + background: linear-gradient( + to right, + .addAlpha(@primary-color, 0.4) [] 0, + .addAlpha(@primary-color, 0) [] 100% + ); + content: ''; + } + } +} diff --git a/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx b/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx new file mode 100644 index 00000000..546eca88 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ConfigTitle/index.tsx @@ -0,0 +1,22 @@ +import { Flex } from 'antd'; +import styles from './index.less'; + +type ConfigTitleProps = { + title: string; +}; + +function ConfigTitle({ title }: ConfigTitleProps) { + return ( + + + {title} + + ); +} + +export default ConfigTitle; diff --git a/react-ui/src/pages/AutoML/components/CopyingText/index.less b/react-ui/src/pages/AutoML/components/CopyingText/index.less new file mode 100644 index 00000000..951b37dd --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CopyingText/index.less @@ -0,0 +1,18 @@ +.copying-text { + display: flex; + flex: 1; + align-items: center; + min-width: 0; + margin-left: 16px; + + &__text { + color: @text-color; + font-size: 15px; + } + + &__icon { + margin-left: 6px; + font-size: 14px; + cursor: pointer; + } +} diff --git a/react-ui/src/pages/AutoML/components/CopyingText/index.tsx b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx new file mode 100644 index 00000000..1df718dd --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx @@ -0,0 +1,33 @@ +import KFIcon from '@/components/KFIcon'; +import { Typography } from 'antd'; + +import styles from './index.less'; + +export type CopyingTextProps = { + text: string; + onCopySuccess?: () => void; + onCopyFailed?: () => void; +}; + +function CopyingText({ text }: CopyingTextProps) { + return ( +
+ + {text} + + +
+ ); +} + +export default CopyingText; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx new file mode 100644 index 00000000..e181d890 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx @@ -0,0 +1,85 @@ +import SubAreaTitle from '@/components/SubAreaTitle'; +import { Col, Form, Input, Row, Select } from 'antd'; +function BasicConfig() { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {index === fields.length - 1 && ( + + )} +
+ + ))} + {fields.length === 0 && ( +
+ +
+ )} + + + )} + + + ); +} + +export default ExecuteConfig; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx new file mode 100644 index 00000000..d96a28e6 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx @@ -0,0 +1,258 @@ +import CodeSelect from '@/components/CodeSelect'; +import ResourceSelect, { ResourceSelectorType } from '@/components/ResourceSelect'; +import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; +import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; +import styles from './index.less'; + +type ExecuteConfigDLCProps = { + disabled?: boolean; +}; + +function ExecuteConfigDLC({ disabled = false }: ExecuteConfigDLCProps) { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + 官方镜像 + 自定义镜像 + 镜像地址 + + + + + + + + {({ getFieldValue }) => { + const imageType = getFieldValue('image_type'); + if (imageType === 1 || imageType === 2) { + return ( + + + + ); + } + }} + + + + + + + + + + + + + + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }, index) => ( + + + + + : + + + +
+ + {index === fields.length - 1 && ( + + )} +
+
+ ))} + {fields.length === 0 && ( + + + + )} + + )} +
+
+ + + + + + + + + + + ); +} + +export default ExecuteConfigDLC; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx new file mode 100644 index 00000000..8f8b7078 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx @@ -0,0 +1,82 @@ +import KFIcon from '@/components/KFIcon'; +import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; +import { Button, Col, Flex, Form, Input, Row } from 'antd'; +import styles from './index.less'; + +type ExecuteConfigMCProps = { + disabled?: boolean; +}; + +function ExecuteConfigMC({ disabled = false }: ExecuteConfigMCProps) { + return ( + <> + + {(fields, { add, remove }) => ( + <> + + + + + +
+ +
Key
+
命令
+
操作
+
+ + {fields.map(({ key, name, ...restField }, index) => ( + + cmd{index + 1} + + + +
+ + {index === fields.length - 1 && ( + + )} +
+
+ ))} + {fields.length === 0 && ( +
+ +
+ )} +
+ + )} + + + ); +} + +export default ExecuteConfigMC; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx new file mode 100644 index 00000000..255a6657 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx @@ -0,0 +1,119 @@ +import SubAreaTitle from '@/components/SubAreaTitle'; +import { Col, Form, InputNumber, Row, Select, Switch } from 'antd'; +function SearchConfig() { + return ( + <> + + + + + + + + + + + + + + : + + + +
+ + {index === fields.length - 1 && ( + + )} +
+ + ))} + {fields.length === 0 && ( + + + + )} + + )} + + + + + + + + + + + + + + + + + 越大越好 + 越小越好 + + + + + + + + + + + + + + + ); +} + +export default TrialConfig; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/index.less b/react-ui/src/pages/AutoML/components/CreateForm/index.less new file mode 100644 index 00000000..57a44b79 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/index.less @@ -0,0 +1,130 @@ +.advanced-config { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } +} + +.command { + width: 83.33%; + margin-bottom: 20px; + border: 1px solid rgba(234, 234, 234, 0.8); + border-radius: 4px; + &__header { + height: 50px; + padding-left: 8px; + color: @text-color; + font-size: @font-size; + background: #f8f8f9; + border-radius: 4px 4px 0px 0px; + + &__name { + flex: none; + width: 100px; + } + &__command { + flex: 1; + margin-right: 15px; + } + + &__operation { + flex: none; + width: 100px; + } + } + &__body { + padding: 8px; + border-bottom: 1px solid rgba(234, 234, 234, 0.8); + + &:last-child { + border-bottom: none; + } + + &__name { + flex: none; + width: 100px; + } + + &__command { + flex: 1; + margin-right: 15px; + margin-bottom: 0 !important; + } + + &__operation { + flex: none; + width: 100px; + } + } + + &__add { + display: flex; + align-items: center; + justify-content: center; + padding: 15px 0; + } +} + +.hyper-parameter { + width: 83.33%; + margin-bottom: 20px; + border: 1px solid rgba(234, 234, 234, 0.8); + border-radius: 4px; + &__header { + height: 50px; + padding-left: 8px; + color: @text-color; + font-size: @font-size; + background: #f8f8f9; + border-radius: 4px 4px 0px 0px; + + &__name, + &__type, + &__space { + flex: 1; + margin-right: 15px; + } + + &__operation { + flex: none; + width: 100px; + } + } + &__body { + padding: 8px; + border-bottom: 1px solid rgba(234, 234, 234, 0.8); + + &:last-child { + border-bottom: none; + } + + &__name, + &__type, + &__space { + flex: 1; + margin-right: 15px; + margin-bottom: 0 !important; + } + + &__operation { + flex: none; + width: 100px; + } + } + + &__add { + display: flex; + align-items: center; + justify-content: center; + padding: 15px 0; + } +} + +.trial-metrics { + width: calc(41.67% - 6px); + margin-bottom: 20px; + padding: 0 20px; + border: 1px dashed #e0e0e0; + border-radius: 8px; +} diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less new file mode 100644 index 00000000..707a9b0d --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less @@ -0,0 +1,30 @@ +.execute-schedule-cell { + display: flex; + flex-direction: row; + align-items: center; + + &__progress { + width: 112px; + height: 16px; + padding: 4px 5px; + background: rgba(96, 107, 122, 0.15); + border-radius: 8px; + + &__bar { + height: 7px; + background: linear-gradient(270deg, #1664ff 0%, rgba(22, 100, 255, 0.1) 100%); + border-radius: 9px; + } + } + + &__text { + margin-left: 6px; + color: @text-color-tertiary; + font-size: 12px; + letter-spacing: 2px; + + strong { + color: @text-color; + } + } +} diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx new file mode 100644 index 00000000..b1b9ae29 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx @@ -0,0 +1,25 @@ +/* + * @Author: 赵伟 + * @Date: 2024-10-29 18:35:41 + * @Description: 实验实例执行进度 + */ + +import styles from './index.less'; + +function ExecuteScheduleCell(status?: any) { + return ( +
+
+
+
+ + 1/2 + +
+ ); +} + +export default ExecuteScheduleCell; diff --git a/react-ui/src/pages/AutoML/components/RunStatusCell/index.less b/react-ui/src/pages/AutoML/components/RunStatusCell/index.less new file mode 100644 index 00000000..b6aba701 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/RunStatusCell/index.less @@ -0,0 +1,19 @@ +.run-status-cell { + color: @text-color; + + &--running { + color: @primary-color; + } + + &--stopped { + color: @abort-color; + } + + &--error { + color: @error-color; + } + + &--pending { + color: @warning-color; + } +} diff --git a/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx b/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx new file mode 100644 index 00000000..a20a2fb6 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx @@ -0,0 +1,44 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-18 18:35:41 + * @Description: 实验运行状态 + */ +import { ServiceRunStatus } from '@/enums'; +import styles from './index.less'; + +export type ServiceRunStatusInfo = { + text: string; + classname: string; +}; + +export const statusInfo: Record = { + [ServiceRunStatus.Init]: { + text: '启动中', + classname: styles['run-status-cell'], + }, + [ServiceRunStatus.Running]: { + classname: styles['run-status-cell--running'], + text: '运行中', + }, + [ServiceRunStatus.Stopped]: { + classname: styles['run-status-cell--stopped'], + text: '已停止', + }, + [ServiceRunStatus.Failed]: { + classname: styles['run-status-cell--error'], + text: '失败', + }, + [ServiceRunStatus.Pending]: { + classname: styles['run-status-cell--pending'], + text: '挂起中', + }, +}; + +function RunStatusCell(status?: ServiceRunStatus | null) { + if (status === null || status === undefined || !statusInfo[status]) { + return --; + } + return {statusInfo[status].text}; +} + +export default RunStatusCell; diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.less b/react-ui/src/pages/AutoML/components/StatusChart/index.less new file mode 100644 index 00000000..be1c816e --- /dev/null +++ b/react-ui/src/pages/AutoML/components/StatusChart/index.less @@ -0,0 +1,8 @@ +.status-chart { + flex: none; + width: 380px; + min-width: 380px; + background: #ffffff; + border: 1px solid @border-color-base; + border-radius: 4px; +} diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx new file mode 100644 index 00000000..db9a3cdd --- /dev/null +++ b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx @@ -0,0 +1,217 @@ +import themes from '@/styles/theme.less'; +import * as echarts from 'echarts'; +import React, { useEffect, useRef } from 'react'; +import ConfigTitle from '../ConfigTitle'; +import styles from './index.less'; + +const color1 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#c73131' }, + { offset: 1, color: '#ff7e96' }, + ], + false, +); + +const color2 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#6ac21d' }, + { offset: 1, color: '#96e850' }, + ], + false, +); +const color3 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#8c8c8c' }, + { offset: 1, color: '#c8c6c6' }, + ], + false, +); +const color4 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#ecb934' }, + { offset: 1, color: '#f0864d' }, + ], + false, +); + +const color5 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: '#7ea9fe' }, + { offset: 1, color: '#1664ff' }, + ], + false, +); + +const color6 = new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { offset: 0, color: 'rgba(255, 255, 255, 0.62)' }, + { offset: 1, color: '#ebf2ff ' }, + ], + false, +); + +export type ExperimentStatistics = { + Failed: number; + Pending: number; + Running: number; + Succeeded: number; + Terminated: number; +}; + +type ExperimentChartProps = { + style?: React.CSSProperties; + chartData: ExperimentStatistics; +}; + +function StatusChart({ chartData, style }: ExperimentChartProps) { + const chartRef = useRef(null); + const total = + chartData.Failed + + chartData.Pending + + chartData.Running + + chartData.Succeeded + + chartData.Terminated; + const options: echarts.EChartsOption = { + title: { + show: true, + left: '29%', + top: 'center', + textAlign: 'center', + text: [`{a|${total}}`, '{b|Trials}'].join('\n'), + textStyle: { + rich: { + a: { + color: themes['textColor'], + fontSize: 20, + fontWeight: 700, + lineHeight: 28, + }, + b: { + color: themes['textColorSecondary'], + fontSize: 10, + fontWeight: 'normal', + }, + }, + }, + }, + tooltip: { + trigger: 'item', + }, + legend: { + top: 'center', + right: '5%', + orient: 'vertical', + icon: 'circle', + itemWidth: 6, + itemGap: 20, + height: 100, + }, + color: [color1, color2, color3, color4, color5], + series: [ + { + type: 'pie', + radius: ['70%', '80%'], + center: ['30%', '50%'], + avoidLabelOverlap: false, + padAngle: 3, + itemStyle: { + borderRadius: 3, + }, + minAngle: 5, + label: { + show: false, + }, + emphasis: { + label: { + show: false, + }, + }, + labelLine: { + show: false, + }, + data: [ + { value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' }, + { value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' }, + { value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' }, + { value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' }, + { value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' }, + ], + }, + { + type: 'pie', + radius: '60%', + center: ['30%', '50%'], + avoidLabelOverlap: false, + label: { + show: false, + }, + tooltip: { + show: false, + }, + emphasis: { + label: { + show: false, + }, + disabled: true, + }, + animation: false, + labelLine: { + show: false, + }, + data: [ + { + value: 100, + itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 }, + }, + ], + }, + ], + }; + + useEffect(() => { + // 创建一个echarts实例,返回echarts实例 + const chart = echarts.init(chartRef.current); + + // 设置图表实例的配置项和数据 + chart.setOption(options); + + // 组件卸载 + return () => { + // myChart.dispose() 销毁实例 + chart.dispose(); + }; + }, []); + + return ( +
+ +
+
+ ); +} + +export default StatusChart; diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts new file mode 100644 index 00000000..56582f9e --- /dev/null +++ b/react-ui/src/pages/AutoML/types.ts @@ -0,0 +1,6 @@ +// 操作类型 +export enum ServiceOperationType { + Create = 'Create', // 创建 + Update = 'Update', // 更新 + Restart = 'Restart', // 重启 +} diff --git a/react-ui/src/utils/clipboard.js b/react-ui/src/utils/clipboard.js new file mode 100644 index 00000000..177fcbce --- /dev/null +++ b/react-ui/src/utils/clipboard.js @@ -0,0 +1,12 @@ +import ClipboardJS from 'clipboard'; +import { message } from "antd"; + +const clipboard = new ClipboardJS('#copying'); + +clipboard.on('success', () => { + message.success('复制成功'); +}); + +clipboard.on('error', () => { + message.error('复制失败'); +}); \ No newline at end of file From 4f8e09a591ee11a15fec58daa38a7a4587b53355 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 6 Nov 2024 14:51:58 +0800 Subject: [PATCH 02/24] =?UTF-8?q?style:=20=E4=BF=AE=E6=94=B9=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=AD=A6=E4=B9=A0=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/KFBreadcrumb/index.tsx | 6 +++ react-ui/src/pages/Application/index.tsx | 6 +++ react-ui/src/pages/AutoML/Create/index.less | 10 +++- .../AutoML/components/AutoMLTable/index.tsx | 2 +- .../AutoML/components/ConfigInfo/index.less | 7 ++- .../AutoML/components/StatusChart/index.tsx | 50 ++++++++++++++++--- 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/react-ui/src/components/KFBreadcrumb/index.tsx b/react-ui/src/components/KFBreadcrumb/index.tsx index c6322025..dc87efc2 100644 --- a/react-ui/src/components/KFBreadcrumb/index.tsx +++ b/react-ui/src/components/KFBreadcrumb/index.tsx @@ -1,3 +1,9 @@ +/* + * @Author: 赵伟 + * @Date: 2024-09-02 08:42:57 + * @Description: 自定义面包屑,暂时不用,使用了 ProBreadcrumb + */ + import { Breadcrumb, type BreadcrumbProps } from 'antd'; import { Link, matchPath, useLocation } from 'umi'; // import routes from '../../../config/config'; // 导入你的路由配置 diff --git a/react-ui/src/pages/Application/index.tsx b/react-ui/src/pages/Application/index.tsx index 65a1efae..20147094 100644 --- a/react-ui/src/pages/Application/index.tsx +++ b/react-ui/src/pages/Application/index.tsx @@ -1,3 +1,9 @@ +/* + * @Author: 赵伟 + * @Date: 2024-09-02 08:42:57 + * @Description: 应用开发 + */ + import IframePage, { IframePageType } from '@/components/IFramePage'; function Application() { diff --git a/react-ui/src/pages/AutoML/Create/index.less b/react-ui/src/pages/AutoML/Create/index.less index ca173288..7c88312e 100644 --- a/react-ui/src/pages/AutoML/Create/index.less +++ b/react-ui/src/pages/AutoML/Create/index.less @@ -32,13 +32,21 @@ } } - .ant-btn.ant-btn-icon-only .anticon { + .ant-btn-variant-text:disabled { + color: rgba(0, 0, 0, 0.25); + } + + .ant-btn-variant-text { color: #565658; + } + + .ant-btn.ant-btn-icon-only .anticon { font-size: 20px; } .anticon-question-circle { margin-top: -12px; + margin-left: 1px !important; color: @text-color-tertiary !important; font-size: 12px !important; } diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx index e4e0f00f..6fd78790 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx @@ -217,7 +217,7 @@ function AutoMLTable() { { title: '操作', dataIndex: 'operation', - width: 400, + width: 250, key: 'operation', render: (_: any, record: ServiceData) => (
diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less index a51e7ddb..babbac65 100644 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less @@ -1,5 +1,6 @@ .config-info { - width: 100%; + flex: 1; + min-width: 0; border: 1px solid @border-color-base; border-radius: 4px; @@ -11,10 +12,11 @@ :global { .kf-basic-info { - gap: 15px 40px; + gap: 15px; width: 100%; &__item { + width: calc((100% - 15px) / 2); &__label { font-size: @font-size; text-align: left; @@ -24,6 +26,7 @@ } } &__value { + min-width: 0; font-size: @font-size; } } diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx index db9a3cdd..2c8b8aa4 100644 --- a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx +++ b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx @@ -4,6 +4,8 @@ import React, { useEffect, useRef } from 'react'; import ConfigTitle from '../ConfigTitle'; import styles from './index.less'; +const colors = ['#c73131', '#6ac21d', '#1664ff', '#f0864d', '#8a8a8a']; + const color1 = new echarts.graphic.LinearGradient( 0, 0, @@ -62,7 +64,7 @@ const color5 = new echarts.graphic.LinearGradient( false, ); -const color6 = new echarts.graphic.LinearGradient( +const circleBgColor = new echarts.graphic.LinearGradient( 0, 0, 0, @@ -130,16 +132,48 @@ function StatusChart({ chartData, style }: ExperimentChartProps) { itemGap: 20, height: 100, }, - color: [color1, color2, color3, color4, color5], + color: colors, //[color1, color2, color3, color4, color5], series: [ { type: 'pie', - radius: ['70%', '80%'], + radius: '80%', + center: ['30%', '50%'], + avoidLabelOverlap: false, + label: { + show: false, + }, + tooltip: { + show: false, + }, + emphasis: { + label: { + show: false, + }, + disabled: true, + }, + animation: false, + labelLine: { + show: false, + }, + data: [ + { + value: 100, + itemStyle: { + color: circleBgColor, + borderColor: 'rgba(22, 100, 255, 0.08)', + borderWidth: 1, + }, + }, + ], + }, + { + type: 'pie', + radius: ['50%', '70%'], center: ['30%', '50%'], avoidLabelOverlap: false, padAngle: 3, itemStyle: { - borderRadius: 3, + borderRadius: 0, }, minAngle: 5, label: { @@ -163,7 +197,7 @@ function StatusChart({ chartData, style }: ExperimentChartProps) { }, { type: 'pie', - radius: '60%', + radius: '40%', center: ['30%', '50%'], avoidLabelOverlap: false, label: { @@ -185,7 +219,11 @@ function StatusChart({ chartData, style }: ExperimentChartProps) { data: [ { value: 100, - itemStyle: { color: color6, borderColor: 'rgba(22, 100, 255, 0.08)', borderWidth: 1 }, + itemStyle: { + color: circleBgColor, + borderColor: 'rgba(22, 100, 255, 0.08)', + borderWidth: 1, + }, }, ], }, From afe3650e9eeb8b642642d723645f6b8969fc80ee Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 09:31:10 +0800 Subject: [PATCH 03/24] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/build-java.sh | 8 +++++++- k8s/build.sh | 12 +++++++++--- k8s/build_and_deploy.sh | 2 +- k8s/deploy.sh | 23 +++++++++++++++++++++-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/k8s/build-java.sh b/k8s/build-java.sh index 7bb2180b..313f7bdf 100755 --- a/k8s/build-java.sh +++ b/k8s/build-java.sh @@ -6,8 +6,14 @@ baseDir="/home/somuns/ci4s" #判断$1是否为all,如果是,则编译所有模块,否则只编译management-platform模块 if [ "$1" == "all" ]; then buildDir=$baseDir -else +elif [ "$1" == "manage"] buildDir="$baseDir/ruoyi-modules/management-platform" +elif [ "$1" == "auth"] + buildDir="$baseDir/ruoyi-auth" +elif [ "$1" == "gateway"] + buildDir="$baseDir/ruoyi-gateway" +elif [ "$1" == "system"] + buildDir="$baseDir/ruoyi-modules/ruoyi-system" fi echo "Building $buildDir" diff --git a/k8s/build.sh b/k8s/build.sh index e8f19b3c..929393d1 100755 --- a/k8s/build.sh +++ b/k8s/build.sh @@ -30,7 +30,7 @@ done echo "branch: $branch" echo "service: $service" -valid_services=("manage-front" "manage" "front" "all") +valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system") if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then echo "Invalid service name: $service" >&2 echo "Valid services are: ${valid_services[*]}" @@ -107,13 +107,19 @@ compile_java() { fi } -if [ "$service" == "manage-front" ] || [ "$service" == "front" ]; then +if [ "$service" == "front" ]; then # 编译前端 compile_front fi +if [ "$service" == "manage-front" ]; then + # 编译前端 + compile_front + # 编译java + compile_java "manage" +fi -if [ "$service" == "manage-front" ] || [ "$service" == "manage" ]; then +if [ "$service" != "manage-front" ] || [ "$service" != "all" ] || [ "$service" != "front" ]; then # 编译java compile_java $service fi diff --git a/k8s/build_and_deploy.sh b/k8s/build_and_deploy.sh index ef582cf3..93f329c2 100755 --- a/k8s/build_and_deploy.sh +++ b/k8s/build_and_deploy.sh @@ -35,7 +35,7 @@ while getopts "b:s:e:h" opt; do esac done -valid_services=("manage-front" "manage" "front" "all") +valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system") if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then echo "Invalid service name: $service" >&2 echo "Valid services are: ${valid_services[*]}" diff --git a/k8s/deploy.sh b/k8s/deploy.sh index 5b3f7887..d8b5e3e7 100755 --- a/k8s/deploy.sh +++ b/k8s/deploy.sh @@ -129,15 +129,34 @@ build_and_deploy() { deploy_service ${yaml_file} } +if [ "$service" == "front" ]; then + build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml" +fi + # 构建和部署 manage 服务 -if [ "$service" == "manage-front" ] || [ "$service" == "manage" ]; then +if [ "$service" == "manage" ]; then build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml" fi +if [ "$service" == "auth" ]; then + #部署认证中心 + build_and_deploy "auth-dockerfile" "172.20.32.187/ci4s/ci4s-auth:${tag}" "k8s-5auth.yaml" +fi + +if [ "$service" == "gateway" ]; then + #部署网关 + build_and_deploy "gateway-dockerfile" "172.20.32.187/ci4s/ci4s-gateway:${tag}" "k8s-4gateway.yaml" +fi + +if [ "$service" == "system" ]; then + #部署系统服务 + build_and_deploy "system-dockerfile" "172.20.32.187/ci4s/ci4s-system:${tag}" "k8s-6system.yaml" +fi # 构建和部署 front 服务 -if [ "$service" == "manage-front" ] || [ "$service" == "front" ]; then +if [ "$service" == "manage-front" ]; then build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml" + build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml" fi From 5e2df58a10849c8c79a01dff2ad9d1d59794c443 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 09:37:30 +0800 Subject: [PATCH 04/24] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/deploy.sh b/k8s/deploy.sh index d8b5e3e7..d06c05d9 100755 --- a/k8s/deploy.sh +++ b/k8s/deploy.sh @@ -27,7 +27,7 @@ done echo "Deploy service: $service, environment: $env" -valid_services=("manage-front" "manage" "front" "all") +valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system") if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then echo "Invalid service name: $service" >&2 echo "Valid services are: ${valid_services[*]}" From 19681ccca1757f1adc476a447d417df4a60b7199 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 09:39:36 +0800 Subject: [PATCH 05/24] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/build_and_deploy.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/k8s/build_and_deploy.sh b/k8s/build_and_deploy.sh index 93f329c2..44ea23fa 100755 --- a/k8s/build_and_deploy.sh +++ b/k8s/build_and_deploy.sh @@ -35,20 +35,6 @@ while getopts "b:s:e:h" opt; do esac done -valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system") -if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then - echo "Invalid service name: $service" >&2 - echo "Valid services are: ${valid_services[*]}" - exit 1 -fi - -valid_envs=("dev" "test") -if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then - echo "Invalid environment: $env" >&2 - echo "Valid environments are: ${valid_envs[*]}" - exit 1 -fi - # 拉取指定分支的最新代码 echo "Checking out and pulling branch $branch..." @@ -68,6 +54,20 @@ fi chmod +777 ${baseDir}/k8s/*.sh +valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system") +if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then + echo "Invalid service name: $service" >&2 + echo "Valid services are: ${valid_services[*]}" + exit 1 +fi + +valid_envs=("dev" "test") +if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then + echo "Invalid environment: $env" >&2 + echo "Valid environments are: ${valid_envs[*]}" + exit 1 +fi + echo "start build" sh ${baseDir}/k8s/build.sh -b ${branch} -s ${service} if [ $? -ne 0 ]; then From ab5252a056db5ef432b8e61d8a8b651e1ace2232 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 10:08:47 +0800 Subject: [PATCH 06/24] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/build-java.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/build-java.sh b/k8s/build-java.sh index 313f7bdf..59544f42 100755 --- a/k8s/build-java.sh +++ b/k8s/build-java.sh @@ -12,7 +12,7 @@ elif [ "$1" == "auth"] buildDir="$baseDir/ruoyi-auth" elif [ "$1" == "gateway"] buildDir="$baseDir/ruoyi-gateway" -elif [ "$1" == "system"] +else buildDir="$baseDir/ruoyi-modules/ruoyi-system" fi From 4f23f78f17053d09219facb1ad059f2a44f3b93f Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 10:10:47 +0800 Subject: [PATCH 07/24] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/build-java.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/k8s/build-java.sh b/k8s/build-java.sh index 59544f42..808beea2 100755 --- a/k8s/build-java.sh +++ b/k8s/build-java.sh @@ -6,13 +6,13 @@ baseDir="/home/somuns/ci4s" #判断$1是否为all,如果是,则编译所有模块,否则只编译management-platform模块 if [ "$1" == "all" ]; then buildDir=$baseDir -elif [ "$1" == "manage"] +elif [ "$1" == "manage"]; then buildDir="$baseDir/ruoyi-modules/management-platform" -elif [ "$1" == "auth"] +elif [ "$1" == "auth"]; then buildDir="$baseDir/ruoyi-auth" -elif [ "$1" == "gateway"] +elif [ "$1" == "gateway"]; then buildDir="$baseDir/ruoyi-gateway" -else +elif [ "$1" == "system"]; then buildDir="$baseDir/ruoyi-modules/ruoyi-system" fi From 2e6c47ccabce9adb196b7f696cb8774e77e30001 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 10:21:52 +0800 Subject: [PATCH 08/24] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/build-java.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/k8s/build-java.sh b/k8s/build-java.sh index 808beea2..bb0884a3 100755 --- a/k8s/build-java.sh +++ b/k8s/build-java.sh @@ -6,13 +6,13 @@ baseDir="/home/somuns/ci4s" #判断$1是否为all,如果是,则编译所有模块,否则只编译management-platform模块 if [ "$1" == "all" ]; then buildDir=$baseDir -elif [ "$1" == "manage"]; then +elif [ "$1" == "manage" ]; then buildDir="$baseDir/ruoyi-modules/management-platform" -elif [ "$1" == "auth"]; then +elif [ "$1" == "auth" ]; then buildDir="$baseDir/ruoyi-auth" -elif [ "$1" == "gateway"]; then +elif [ "$1" == "gateway" ]; then buildDir="$baseDir/ruoyi-gateway" -elif [ "$1" == "system"]; then +elif [ "$1" == "system" ]; then buildDir="$baseDir/ruoyi-modules/ruoyi-system" fi From 1295ba0813b5d22e0be857e6c387153d6ef0555d Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 10:54:41 +0800 Subject: [PATCH 09/24] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/build.sh b/k8s/build.sh index 929393d1..13298d60 100755 --- a/k8s/build.sh +++ b/k8s/build.sh @@ -119,7 +119,7 @@ if [ "$service" == "manage-front" ]; then compile_java "manage" fi -if [ "$service" != "manage-front" ] || [ "$service" != "all" ] || [ "$service" != "front" ]; then +if [ "$service" != "manage-front" ] && [ "$service" != "all" ] && [ "$service" != "front" ]; then # 编译java compile_java $service fi From 5a479704225a971009d8caa40003b3bfc7d7c8f6 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 11:41:27 +0800 Subject: [PATCH 10/24] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E6=97=B6=E5=81=9C=E6=AD=A2jupyter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/platform/service/DevEnvironmentService.java | 2 +- .../ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java index 055b60dc..4562c7d4 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/DevEnvironmentService.java @@ -54,5 +54,5 @@ public interface DevEnvironmentService { */ boolean deleteById(Integer id); - String removeById(Integer id); + String removeById(Integer id) throws Exception; } diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java index 7992f089..606d35aa 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java @@ -138,7 +138,7 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { } @Override - public String removeById(Integer id) { + public String removeById(Integer id) throws Exception { DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id); if (devEnvironment == null){ return "开发环境信息不存在"; @@ -152,6 +152,7 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService { return "无权限删除该开发环境"; } + jupyterService.stopJupyterService(id); devEnvironment.setState(0); return this.devEnvironmentDao.update(devEnvironment)>0?"删除成功":"删除失败"; } From a4478f591497aea39e705347cbfff3716bfa573d Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Thu, 7 Nov 2024 11:49:20 +0800 Subject: [PATCH 11/24] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E6=97=B6=E5=81=9C=E6=AD=A2jupyter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/devEnvironment/DevEnvironmentController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java index 7667f1ce..0a3b608d 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/devEnvironment/DevEnvironmentController.java @@ -83,7 +83,7 @@ public class DevEnvironmentController extends BaseController { * @return 删除是否成功 */ @DeleteMapping("{id}") - public GenericsAjaxResult deleteById(@PathVariable("id") Integer id) { + public GenericsAjaxResult deleteById(@PathVariable("id") Integer id) throws Exception { return genericsSuccess(this.devEnvironmentService.removeById(id)); } From 1492af2fab731c3308f17ea3aa8e2affa33aebe7 Mon Sep 17 00:00:00 2001 From: chenzhihang <709011834@qq.com> Date: Sat, 16 Nov 2024 16:23:10 +0800 Subject: [PATCH 12/24] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-auth/src/main/resources/bootstrap.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ruoyi-auth/src/main/resources/bootstrap.yml b/ruoyi-auth/src/main/resources/bootstrap.yml index 95a9dfdd..4dfb267d 100644 --- a/ruoyi-auth/src/main/resources/bootstrap.yml +++ b/ruoyi-auth/src/main/resources/bootstrap.yml @@ -33,9 +33,4 @@ spring: refresh: true - data-id: ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} group: DEFAULT_GROUP - refresh: true - - redis: - host: 172.20.32.150 - port: 6379 - password: \ No newline at end of file + refresh: true \ No newline at end of file From 9eeed3b52dbf8e1ec2f805bc858ff5e8be99544b Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 18 Nov 2024 08:57:26 +0800 Subject: [PATCH 13/24] =?UTF-8?q?fix:=20=E6=B5=81=E6=B0=B4=E7=BA=BF?= =?UTF-8?q?=E5=A6=82=E6=9E=9C=E6=B2=A1=E6=9C=89=E8=BE=93=E5=85=A5=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E9=9A=90=E8=97=8F=E5=85=B6=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PipelineNodeDrawer/index.tsx | 112 ++++++++++-------- 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx index 2eae5c64..ba1fe04d 100644 --- a/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx +++ b/react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx @@ -491,59 +491,69 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete ))} -
- -
- {inParametersList.map((item) => ( - -
- - {item.value.type === 'select' ? ( - - ) : ( - - )} + {/* 输入参数 */} + {inParametersList.length > 0 && ( + <> +
+ +
+ {inParametersList.map((item) => ( + +
+ + {item.value.type === 'select' ? ( + + ) : ( + + )} + + {item.value.type === 'ref' && ( + + + + )} +
- {item.value.type === 'ref' && ( - - - - )} + ))} + + )} + {/* 输出参数 */} + {outParametersList.length > 0 && ( + <> +
+
-
- ))} -
- -
- {outParametersList.map((item) => ( - - - - ))} + {outParametersList.map((item) => ( + + + + ))} + + )} ); From b07f850c19447b6e986e1bbc7ab62ced772a337e Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 19 Nov 2024 17:08:48 +0800 Subject: [PATCH 14/24] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=8D=87=E7=BA=A7=E4=B9=8B=E5=90=8E=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/overrides.less | 8 ++++++++ react-ui/src/styles/menu.less | 33 +++++++++++++++++---------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/react-ui/src/overrides.less b/react-ui/src/overrides.less index f676890e..e129b4a7 100644 --- a/react-ui/src/overrides.less +++ b/react-ui/src/overrides.less @@ -204,6 +204,14 @@ margin-inline-start: 12px; } +.ant-pro-layout .ant-pro-sider-logo-collapsed { + padding: 16px 12px; +} + +.ant-pro-base-menu-inline .ant-pro-base-menu-inline-menu-item { + transition: padding 0.1s !important; +} + // PageContainer 里的 ProTable 只滑动内容区域 .system-menu.ant-pro-page-container { height: 100%; diff --git a/react-ui/src/styles/menu.less b/react-ui/src/styles/menu.less index 1d547ff1..02fe0c17 100644 --- a/react-ui/src/styles/menu.less +++ b/react-ui/src/styles/menu.less @@ -15,16 +15,6 @@ display: none !important; margin-left: 0 !important; } - - &:hover { - .anticon.kf-menu-item__default-icon { - display: none !important; - } - .anticon.kf-menu-item__active-icon { - display: inline !important; - opacity: 1; - } - } } } @@ -42,18 +32,29 @@ } } +.ant-menu-submenu .ant-menu-submenu-title:hover, +.ant-menu-item:hover { + color: @primary-color !important; + + .kf-menu-item { + .anticon.kf-menu-item__default-icon { + display: none !important; + } + + .anticon.kf-menu-item__active-icon { + display: inline !important; + opacity: 1; + } + } +} + .ant-pro-base-menu-vertical-collapsed { .kf-menu-item { justify-content: center; + width: 100%; .kf-menu-item__name { display: none !important; } } } - -.ant-menu-submenu { - .ant-menu-submenu-title:hover { - color: @primary-color !important; - } -} From e844fc614e39164a323ab79ebbc76de88fb36ff4 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 25 Nov 2024 15:48:42 +0800 Subject: [PATCH 15/24] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E8=81=94=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/config/routes.ts | 7 +- react-ui/src/enums/index.ts | 24 + react-ui/src/pages/Authorize/index.tsx | 1 - react-ui/src/pages/AutoML/Create/index.tsx | 214 ++++---- react-ui/src/pages/AutoML/List/index.tsx | 80 ++- .../AutoML/components/AutoMLTable/index.tsx | 6 +- .../components/CreateForm/BasicConfig.tsx | 31 +- .../components/CreateForm/DatasetConfig.tsx | 59 +++ .../components/CreateForm/ExecuteConfig.tsx | 455 +++++++++++++++++- .../CreateForm/ExecuteConfigDLC.tsx | 29 +- .../components/CreateForm/TrialConfig.tsx | 263 ++++------ .../components/CreateForm/UploadConfig.tsx | 80 +++ .../AutoML/components/CreateForm/index.less | 11 + react-ui/src/pages/AutoML/types.ts | 52 +- react-ui/src/services/autoML.js | 55 +++ react-ui/src/utils/functional.ts | 10 + react-ui/src/utils/index.ts | 15 + 17 files changed, 995 insertions(+), 397 deletions(-) create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx create mode 100644 react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx create mode 100644 react-ui/src/services/autoML.js diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 189efc8a..c58b182e 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -160,10 +160,15 @@ export default [ component: './AutoML/Info/index', }, { - name: '创建自动机器学习', + name: '创建实验', path: 'create', component: './AutoML/Create/index', }, + { + name: '编辑实验', + path: 'edit/:id', + component: './AutoML/Create/index', + }, ], }, ], diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index e489515c..05e14b34 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -71,6 +71,7 @@ export enum DevEditorStatus { Unknown = 'Unknown', // 未启动 } +// 服务类型 export enum ServiceType { Video = 'video', Image = 'image', @@ -84,3 +85,26 @@ export const serviceTypeOptions = [ { label: '音频', value: ServiceType.Audio }, { label: '文本', value: ServiceType.Text }, ]; + +// 自动化任务类型 +export enum AutoMLTaskType { + Classification = 'classification', + Regression = 'regression', +} + +// 自动化任务集成策略 +export enum AutoMLEnsembleClass { + Default = 'default', + SingleBest = 'SingleBest', +} + +// 自动化任务重采样策略 +export enum AutoMLResamplingStrategy { + Holdout = 'holdout', + CrossValid = 'crossValid', +} + +export const resamplingStrategyOptions = [ + { label: 'holdout', value: AutoMLResamplingStrategy.Holdout }, + { label: 'crossValid', value: AutoMLResamplingStrategy.CrossValid }, +]; diff --git a/react-ui/src/pages/Authorize/index.tsx b/react-ui/src/pages/Authorize/index.tsx index f3624f32..e42a0f1b 100644 --- a/react-ui/src/pages/Authorize/index.tsx +++ b/react-ui/src/pages/Authorize/index.tsx @@ -22,7 +22,6 @@ function Authorize() { code, }; const [res] = await to(loginByOauth2Req(params)); - debugger; if (res && res.data) { const { access_token, expires_in } = res.data; setSessionToken(access_token, access_token, expires_in); diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 9ac79d57..743d8377 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -4,147 +4,116 @@ * @Description: 创建服务版本 */ import PageTitle from '@/components/PageTitle'; -import { type ParameterInputObject } from '@/components/ResourceSelect'; -import { useComputingResource } from '@/hooks/resource'; -import { - createServiceVersionReq, - getServiceInfoReq, - updateServiceVersionReq, -} from '@/services/modelDeployment'; -import { changePropertyName } from '@/utils'; + +import { AutoMLTaskType } from '@/enums'; +import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/autoML'; +import { parseJsonText, trimCharacter } from '@/utils'; +import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; -import SessionStorage from '@/utils/sessionStorage'; import { useNavigate, useParams } from '@umijs/max'; import { App, Button, Form } from 'antd'; -import { omit, pick } from 'lodash'; -import { useEffect, useState } from 'react'; +import { omit } from 'lodash'; +import { useEffect } from 'react'; import BasicConfig from '../components/CreateForm/BasicConfig'; +import DatasetConfig from '../components/CreateForm/DatasetConfig'; import ExecuteConfig from '../components/CreateForm/ExecuteConfig'; -import SearchConfig from '../components/CreateForm/SearchConfig'; import TrialConfig from '../components/CreateForm/TrialConfig'; -import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types'; +import { AutoMLData, FormData } from '../types'; import styles from './index.less'; -// 表单数据 -export type FormData = { - service_name: string; // 服务名称 - version: string; // 服务版本 - description: string; // 描述 - model: ParameterInputObject; // 模型 - image: ParameterInputObject; // 镜像 - code_config: ParameterInputObject; // 代码 - resource: string; // 资源规格 - replicas: string; // 副本数量 - mount_path: string; // 模型路径 - env_variables: { key: string; value: string }[]; // 环境变量 -}; - function CreateAutoML() { const navigate = useNavigate(); const [form] = Form.useForm(); - const [resourceStandardList, filterResourceStandard] = useComputingResource(); - const [operationType, setOperationType] = useState(ServiceOperationType.Create); - const { message } = App.useApp(); - const [serviceInfo, setServiceInfo] = useState(undefined); - const [versionInfo, setVersionInfo] = useState(undefined); const params = useParams(); - const id = params.id; + const id = safeInvoke(Number)(params.id); useEffect(() => { - const res: - | (ServiceVersionData & { - operationType: ServiceOperationType; - }) - | undefined = SessionStorage.getItem(SessionStorage.serviceVersionInfoKey, true); - if (res) { - setOperationType(res.operationType); - - setVersionInfo(res); - let model, codeConfig, envVariables; - if (res.model && typeof res.model === 'object') { - model = changePropertyName(res.model, { show_value: 'showValue' }); - // 接口返回是数据没有 value 值,但是 form 需要 value - model.value = model.showValue; - } - if (res.code_config && typeof res.code_config === 'object') { - codeConfig = changePropertyName(res.code_config, { show_value: 'showValue' }); - // 接口返回是数据没有 value 值,但是 form 需要 value - codeConfig.value = codeConfig.showValue; - } - if (res.env_variables && typeof res.env_variables === 'object') { - envVariables = Object.entries(res.env_variables).map(([key, value]) => ({ - key, - value, - })); - } - - const formData = { - ...omit(res, 'model', 'code_config', 'env_variables'), - model: model, - code_config: codeConfig, - env_variables: envVariables, - }; - form.setFieldsValue(formData); + if (id) { + getAutoMLInfo(); } - return () => { - SessionStorage.removeItem(SessionStorage.serviceVersionInfoKey); - }; - }, []); + }, [id]); // 获取服务详情 - const getServiceInfo = async () => { - const [res] = await to(getServiceInfoReq(id)); + const getAutoMLInfo = async () => { + const [res] = await to(getDatasetInfoReq({ id })); if (res && res.data) { - setServiceInfo(res.data); - form.setFieldsValue({ - service_name: res.data.service_name, - }); + const autoMLInfo: AutoMLData = res.data; + const { + include_classifier: include_classifier_str, + include_feature_preprocessor: include_feature_preprocessor_str, + include_regressor: include_regressor_str, + exclude_classifier: exclude_classifier_str, + exclude_feature_preprocessor: exclude_feature_preprocessor_str, + exclude_regressor: exclude_regressor_str, + metrics: metrics_str, + } = autoMLInfo; + const include_classifier = include_classifier_str?.split(',').filter(Boolean); + const include_feature_preprocessor = include_feature_preprocessor_str + ?.split(',') + .filter(Boolean); + const include_regressor = include_regressor_str?.split(',').filter(Boolean); + const exclude_classifier = exclude_classifier_str?.split(',').filter(Boolean); + const exclude_feature_preprocessor = exclude_feature_preprocessor_str + ?.split(',') + .filter(Boolean); + const exclude_regressor = exclude_regressor_str?.split(',').filter(Boolean); + const metricsObj = safeInvoke(parseJsonText)(metrics_str) ?? {}; + const metrics = Object.entries(metricsObj).map(([key, value]) => ({ + name: key, + value, + })); + + const formData = { + ...autoMLInfo, + include_classifier, + include_feature_preprocessor, + include_regressor, + exclude_classifier, + exclude_feature_preprocessor, + exclude_regressor, + metrics, + }; + + form.setFieldsValue(formData); } }; // 创建版本 - const createServiceVersion = async (formData: FormData) => { - const envList = formData['env_variables'] ?? []; - const image = formData['image']; - const model = formData['model']; - const codeConfig = formData['code_config']; - const envVariables = envList.reduce((acc, cur) => { - acc[cur.key] = cur.value; + const createExperiment = async (formData: FormData) => { + const include_classifier = formData['include_classifier']?.join(','); + const include_feature_preprocessor = formData['include_feature_preprocessor']?.join(','); + const include_regressor = formData['include_regressor']?.join(','); + const exclude_classifier = formData['exclude_classifier']?.join(','); + const exclude_feature_preprocessor = formData['exclude_feature_preprocessor']?.join(','); + const exclude_regressor = formData['exclude_regressor']?.join(','); + const metrics = formData['metrics']?.reduce((acc, cur) => { + acc[cur.name] = cur.value; return acc; - }, {} as Record); + }, {} as Record); + const target_columns = trimCharacter(formData['target_columns'], ','); // 根据后台要求,修改表单数据 const object = { - ...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']), - replicas: Number(formData.replicas), - env_variables: envVariables, - image: image.value, - model: changePropertyName( - pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), - { showValue: 'show_value' }, - ), - code_config: changePropertyName(pick(codeConfig, ['code_path', 'branch', 'showValue']), { - showValue: 'show_value', - }), - service_id: serviceInfo?.id, + ...omit(formData), + include_classifier, + include_feature_preprocessor, + include_regressor, + exclude_classifier, + exclude_feature_preprocessor, + exclude_regressor, + metrics: metrics ? JSON.stringify(metrics) : undefined, + target_columns, }; - const params = - operationType === ServiceOperationType.Create - ? object - : { - id: versionInfo?.id, - rerun: operationType === ServiceOperationType.Restart ? true : false, - deployment_name: versionInfo?.deployment_name, - ...object, - }; - - const request = - operationType === ServiceOperationType.Create - ? createServiceVersionReq - : updateServiceVersionReq; + const params = id + ? { + id: id, + ...object, + } + : object; + const request = id ? updateAutoMLReq : addAutoMLReq; const [res] = await to(request(params)); if (res) { message.success('操作成功'); @@ -154,7 +123,7 @@ function CreateAutoML() { // 提交 const handleSubmit = (values: FormData) => { - console.log('values', values); + createExperiment(values); }; // 取消 @@ -162,15 +131,12 @@ function CreateAutoML() { navigate(-1); }; - const disabled = operationType !== ServiceOperationType.Create; + const disabled = id !== null || id !== undefined; let buttonText = '新建'; - let title = '新增服务版本'; - if (operationType === ServiceOperationType.Update) { - title = '更新服务版本'; + let title = '新增实验'; + if (id) { + title = '更新实验'; buttonText = '更新'; - } else if (operationType === ServiceOperationType.Restart) { - title = '重启服务版本'; - buttonText = '重启'; } return ( @@ -180,22 +146,22 @@ function CreateAutoML() {
- + @@ -279,7 +267,7 @@ function AutoMLList() { diff --git a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx index e181d890..dbf32e32 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx @@ -1,5 +1,5 @@ import SubAreaTitle from '@/components/SubAreaTitle'; -import { Col, Form, Input, Row, Select } from 'antd'; +import { Col, Form, Input, Row } from 'antd'; function BasicConfig() { return ( <> @@ -11,43 +11,34 @@ function BasicConfig() {
- + - + {/* - + */} ); } diff --git a/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx new file mode 100644 index 00000000..79984a41 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx @@ -0,0 +1,59 @@ +import ResourceSelect, { + ResourceSelectorType, + requiredValidator, +} from '@/components/ResourceSelect'; +import SubAreaTitle from '@/components/SubAreaTitle'; +import { Col, Form, Input, Row } from 'antd'; + +function DatasetConfig() { + return ( + <> + + + + + + + + + + + + + + + + + ); +} + +export default DatasetConfig; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx index 4ce0143e..e8ef7d65 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx @@ -1,19 +1,110 @@ -import KFIcon from '@/components/KFIcon'; import SubAreaTitle from '@/components/SubAreaTitle'; -import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; -import { Button, Col, Flex, Form, Input, Radio, Row } from 'antd'; -import ExecuteConfigDLC from './ExecuteConfigDLC'; -import ExecuteConfigMC from './ExecuteConfigMC'; -import styles from './index.less'; +import { + AutoMLEnsembleClass, + AutoMLResamplingStrategy, + AutoMLTaskType, + resamplingStrategyOptions, +} from '@/enums'; +import { Col, Form, Input, InputNumber, Radio, Row, Select, Switch } from 'antd'; -type ExecuteConfigProps = { - disabled?: boolean; -}; +// 分类算法 +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', +].map((name) => ({ label: name, value: name })); -function ExecuteConfig({ disabled = false }: ExecuteConfigProps) { +// 回归算法 +const regressorAlgorithms = [ + 'adaboost', + 'ard_regression', + 'decision_tree', + 'extra_trees', + 'gaussian_process', + 'gradient_boosting', + 'k_nearest_neighbors', + 'liblinear_svr', + 'libsvm_svr', + 'mlp', + 'random_forest', + 'sgd', +].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 })); + +// 分类指标 +export const classificationMetrics = [ + 'accuracy', + 'balanced_accuracy', + 'roc_auc', + 'average_precision', + 'log_loss', + 'precision_macro', + 'precision_micro', + 'precision_samples', + 'precision_weighted', + 'recall_macro', + 'recall_micro', + 'recall_samples', + 'recall_weighted', + 'f1_macro', + 'f1_micro', + 'f1_samples', + 'f1_weighted', +].map((name) => ({ label: name, value: name })); + +// 回归指标 +export const regressionMetrics = [ + 'mean_absolute_error', + 'mean_squared_error', + 'root_mean_squared_error', + 'mean_squared_log_error', + 'median_absolute_error', + 'r2', +].map((name) => ({ label: name, value: name })); + +function ExecuteConfig() { const form = Form.useFormInstance(); - const image_type = Form.useWatch('image_type', form); - console.log(image_type); + const task_type = Form.useWatch('task_type', form); + const include_classifier = Form.useWatch('include_classifier', form); + const exclude_classifier = Form.useWatch('exclude_classifier', form); + const include_regressor = Form.useWatch('include_regressor', form); + const exclude_regressor = Form.useWatch('exclude_regressor', form); + const include_feature_preprocessor = Form.useWatch('include_feature_preprocessor', form); + const exclude_feature_preprocessor = Form.useWatch('exclude_feature_preprocessor', form); return ( <> @@ -26,27 +117,349 @@ function ExecuteConfig({ disabled = false }: ExecuteConfigProps) { - DLC - MaxCompute + 分类 + 回归 - + + + + + 0} + mode="multiple" + showSearch + /> + + + + + {({ getFieldValue }) => { - return getFieldValue('execute_type') === 'DLC' ? ( - + return getFieldValue('task_type') === AutoMLTaskType.Classification ? ( + <> + + + + 0} + showSearch + /> + + + + ) : ( - + <> + + + + 0} + showSearch + /> + + + + ); }} - + + + + + 集成模型 + 单一最佳模型 + + + + + + + {({ getFieldValue }) => { + return getFieldValue('ensemble_class') === AutoMLEnsembleClass.Default ? ( + <> + + + + + + + + + + + + + + + + + ) : null; + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* {(fields, { add, remove }) => ( <> @@ -126,7 +539,7 @@ function ExecuteConfig({ disabled = false }: ExecuteConfigProps) { )} - + */} ); } diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx index d96a28e6..6b2c63e6 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx @@ -1,5 +1,8 @@ import CodeSelect from '@/components/CodeSelect'; -import ResourceSelect, { ResourceSelectorType } from '@/components/ResourceSelect'; +import ResourceSelect, { + requiredValidator, + ResourceSelectorType, +} from '@/components/ResourceSelect'; import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; import styles from './index.less'; @@ -62,12 +65,12 @@ function ExecuteConfigDLC({ disabled = false }: ExecuteConfigDLCProps) { -
- -
- - - - - - - - - - - - - - - {(fields, { add, remove }) => ( - <> - {fields.map(({ key, name, ...restField }, index) => ( - - - - - : - - - -
+ +
+ + + + + + + + + + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }, index) => ( + + + - - - - - - - - - 越大越好 - 越小越好 - - - - - + )} + + + ))} + {fields.length === 0 && ( + + + + )} + + )} + + + + - - + + - + + 越大越好 + 越小越好 + diff --git a/react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx new file mode 100644 index 00000000..429be535 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx @@ -0,0 +1,80 @@ +import { getAccessToken } from '@/access'; +import KFIcon from '@/components/KFIcon'; +import SubAreaTitle from '@/components/SubAreaTitle'; +import { getFileListFromEvent } from '@/utils/ui'; +import { Button, Col, Form, Input, Row, Upload, type UploadProps } from 'antd'; +import { useState } from 'react'; +import styles from './index.less'; + +function UploadConfig() { + const [uuid] = useState(Date.now()); + // 上传组件参数 + const uploadProps: UploadProps = { + action: '/api/mmp/autoML/upload', + headers: { + Authorization: getAccessToken() || '', + }, + defaultFileList: [], + }; + + return ( + <> + + + + + + + + + + + + + + + + + + +
只允许上传 .csv 格式文件
+
+
+ + ); +} + +export default UploadConfig; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/index.less b/react-ui/src/pages/AutoML/components/CreateForm/index.less index 57a44b79..8e8414a5 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/index.less +++ b/react-ui/src/pages/AutoML/components/CreateForm/index.less @@ -128,3 +128,14 @@ border: 1px dashed #e0e0e0; border-radius: 8px; } + +.upload-tip { + margin-top: 5px; + color: @text-color-secondary; + font-size: 14px; +} + +.upload-button { + height: 46px; + font-size: 15px; +} diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts index 56582f9e..59496c64 100644 --- a/react-ui/src/pages/AutoML/types.ts +++ b/react-ui/src/pages/AutoML/types.ts @@ -1,6 +1,54 @@ +import { type ParameterInputObject } from '@/components/ResourceSelect'; + // 操作类型 -export enum ServiceOperationType { +export enum OperationType { Create = 'Create', // 创建 Update = 'Update', // 更新 - Restart = 'Restart', // 重启 } + +// 表单数据 +export type FormData = { + ml_name: string; // 实验名称 + ml_description: string; // 实验描述 + ensemble_class?: string; // 集成构建 + ensemble_nbest?: string; + ensemble_size?: number; + include_classifier?: string[]; + include_feature_preprocessor?: string[]; + include_regressor?: string[]; + exclude_classifier?: string[]; + exclude_feature_preprocessor?: string[]; + exclude_regressor?: string[]; + max_models_on_disc?: number; + memory_limit?: number; + metric_name?: string; + greater_is_better: boolean; + per_run_time_limit?: number; + resampling_strategy?: string; + scoring_functions?: string; + shuffle?: boolean; + seed?: number; + target_columns: string; + task_type: string; + test_size?: number; + train_size?: number; + time_left_for_this_task: number; + tmp_folder?: string; + metrics?: { name: string; value: number }[]; + dataset: ParameterInputObject; // 模型 +}; + +export type AutoMLData = { + id: string; + progress: number; + run_state: string; + state: number; + metrics?: string; + include_classifier?: string; + include_feature_preprocessor?: string; + include_regressor?: string; + exclude_classifier?: string; + exclude_feature_preprocessor?: string; + exclude_regressor?: string; + dataset?: string; +}; diff --git a/react-ui/src/services/autoML.js b/react-ui/src/services/autoML.js new file mode 100644 index 00000000..63f4cd70 --- /dev/null +++ b/react-ui/src/services/autoML.js @@ -0,0 +1,55 @@ +/* + * @Author: 赵伟 + * @Date: 2024-11-18 10:18:27 + * @Description: 自动机器学习请求 + */ + +import { request } from '@umijs/max'; + + +// 分页查询自动学习 +export function getAutoMLListReq(params) { + return request(`/api/mmp/autoML`, { + method: 'GET', + params, + }); +} + +// 查询自动学习详情 +export function getDatasetInfoReq(params) { + return request(`/api/mmp/autoML/getAutoMlDetail`, { + method: 'GET', + params, + }); +} + +// 新增自动学习 +export function addAutoMLReq(data) { + return request(`/api/mmp/autoML`, { + method: 'POST', + data, + }); +} + +// 编辑自动学习 +export function updateAutoMLReq(data) { + return request(`/api/mmp/autoML`, { + method: 'PUT', + data, + }); +} + +// 删除自动学习 +export function deleteAutoMLReq(id) { + return request(`/api/mmp/autoML/${id}`, { + method: 'DELETE', + }); +} + +// 运行自动学习 +export function runAutoMLReq(id) { + return request(`/api/mmp/autoML/${id}`, { + method: 'POST', + params, + }); +} \ No newline at end of file diff --git a/react-ui/src/utils/functional.ts b/react-ui/src/utils/functional.ts index 01514db7..6128c897 100644 --- a/react-ui/src/utils/functional.ts +++ b/react-ui/src/utils/functional.ts @@ -4,6 +4,16 @@ * @Description: 函数式编程 */ +/** + * Safely invokes a function with a given value, returning the result of the + * function or the provided value if it is `undefined` or `null`. + * + * @template T - The type of the input value. + * @template M - The type of the output value. + * @param {function} fn - The function to be invoked with the input value. + * @returns {function} A function that takes a value, invokes `fn` with it if + * it's not `undefined` or `null`, and returns the result or the original value. + */ export function safeInvoke( fn: (value: T) => M | undefined | null, ): (value: T | undefined | null) => M | undefined | null { diff --git a/react-ui/src/utils/index.ts b/react-ui/src/utils/index.ts index 67ca10d2..8feb6d51 100644 --- a/react-ui/src/utils/index.ts +++ b/react-ui/src/utils/index.ts @@ -241,3 +241,18 @@ export const tableSorter = (a: any, b: any) => { } return 0; }; + +/** + * Trim the given character from both ends of the given string. + * + * @param {string} ch - the character to trim + * @param {string} str - the string to trim + * @return {string} the trimmed string + */ +export const trimCharacter = (str: string, ch: string): string => { + if (str === null || str === undefined) { + return str; + } + const reg = new RegExp(`^${ch}|${ch}$`, 'g'); + return str.trim().replace(reg, ''); +}; From 865af276f321badf2bebb6c0684957d168acd228 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Tue, 26 Nov 2024 14:03:29 +0800 Subject: [PATCH 16/24] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/pages/AutoML/Create/index.tsx | 27 +- react-ui/src/pages/AutoML/List/index.tsx | 29 +- .../components/CreateForm/ExecuteConfig.tsx | 2 +- .../components/CreateForm/TrialConfig.tsx | 31 +- .../AutoML/components/CreateForm/index.less | 266 +++++++++--------- .../components/ExecuteScheduleCell/index.tsx | 9 +- react-ui/src/pages/AutoML/types.ts | 5 +- react-ui/src/utils/sessionStorage.ts | 2 + 8 files changed, 200 insertions(+), 171 deletions(-) diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 743d8377..26e1866c 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -10,6 +10,7 @@ import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/aut import { parseJsonText, trimCharacter } from '@/utils'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; import { useNavigate, useParams } from '@umijs/max'; import { App, Button, Form } from 'antd'; import { omit } from 'lodash'; @@ -29,13 +30,25 @@ function CreateAutoML() { const id = safeInvoke(Number)(params.id); useEffect(() => { - if (id) { - getAutoMLInfo(); + // 复制和新建 + const recordId = SessionStorage.getItem(SessionStorage.autoMLRecordIDKey); + if (recordId && !Number.isNaN(Number(recordId))) { + getAutoMLInfo(Number(recordId), true); + } + return () => { + SessionStorage.removeItem(SessionStorage.autoMLRecordIDKey); + }; + }, []); + + useEffect(() => { + // 编辑 + if (id && !Number.isNaN(id)) { + getAutoMLInfo(id, false); } }, [id]); // 获取服务详情 - const getAutoMLInfo = async () => { + const getAutoMLInfo = async (id: number, isCopy = false) => { const [res] = await to(getDatasetInfoReq({ id })); if (res && res.data) { const autoMLInfo: AutoMLData = res.data; @@ -47,6 +60,8 @@ function CreateAutoML() { exclude_feature_preprocessor: exclude_feature_preprocessor_str, exclude_regressor: exclude_regressor_str, metrics: metrics_str, + ml_name: ml_name_str, + ...rest } = autoMLInfo; const include_classifier = include_classifier_str?.split(',').filter(Boolean); const include_feature_preprocessor = include_feature_preprocessor_str @@ -63,9 +78,10 @@ function CreateAutoML() { name: key, value, })); + const ml_name = isCopy ? `${ml_name_str}-copy` : ml_name_str; const formData = { - ...autoMLInfo, + ...rest, include_classifier, include_feature_preprocessor, include_regressor, @@ -73,6 +89,7 @@ function CreateAutoML() { exclude_feature_preprocessor, exclude_regressor, metrics, + ml_name, }; form.setFieldsValue(formData); @@ -135,7 +152,7 @@ function CreateAutoML() { let buttonText = '新建'; let title = '新增实验'; if (id) { - title = '更新实验'; + title = '编辑实验'; buttonText = '更新'; } diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx index 9ef6eda6..be76e591 100644 --- a/react-ui/src/pages/AutoML/List/index.tsx +++ b/react-ui/src/pages/AutoML/List/index.tsx @@ -9,6 +9,7 @@ import { useCacheState } from '@/hooks/pageCacheState'; import { deleteAutoMLReq, getAutoMLListReq } from '@/services/autoML'; import themes from '@/styles/theme.less'; import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; import tableCellRender, { TableCellValueType } from '@/utils/table'; import { modalConfirm } from '@/utils/ui'; import { useNavigate } from '@umijs/max'; @@ -90,7 +91,7 @@ function AutoMLList() { // 处理删除 const handleAutoMLDelete = (record: AutoMLData) => { modalConfirm({ - title: '删除后,该服务将不可恢复', + title: '删除后,该实验将不可恢复', content: '是否确认删除?', onOk: () => { deleteService(record); @@ -99,15 +100,21 @@ function AutoMLList() { }; // 创建、编辑 - const createService = (record?: AutoMLData) => { + const createService = (record?: AutoMLData, isCopy: boolean = false) => { setCacheState({ pagination, searchText, }); if (record) { - navigate(`/pipeline/autoML/edit/${record.id}`); + if (isCopy) { + SessionStorage.setItem(SessionStorage.autoMLRecordIDKey, record.id, false); + navigate(`/pipeline/autoML/create`); + } else { + navigate(`/pipeline/autoML/edit/${record.id}`); + } } else { + SessionStorage.setItem(SessionStorage.autoMLRecordIDKey, '', false); navigate(`/pipeline/autoML/create`); } }; @@ -139,7 +146,7 @@ function AutoMLList() { title: '序号', dataIndex: 'index', key: 'index', - width: '20%', + width: 80, render: tableCellRender(false, TableCellValueType.Index, { page: pagination.current! - 1, pageSize: pagination.pageSize!, @@ -166,7 +173,7 @@ function AutoMLList() { title: '状态', dataIndex: 'run_state', key: 'run_state', - width: '20%', + width: 100, render: RunStatusCell, }, { @@ -207,7 +214,7 @@ function AutoMLList() { size="small" key="edit" icon={} - onClick={() => createService(record)} + onClick={() => createService(record, false)} > 编辑 @@ -216,17 +223,11 @@ function AutoMLList() { size="small" key="copy" icon={} - onClick={() => toDetail(record)} + onClick={() => createService(record, true)} > 复制 - - + form.resetFields(['metrics'])}> 分类 回归 diff --git a/react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx index d65b62e8..9201009a 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx @@ -8,6 +8,14 @@ import styles from './index.less'; function TrialConfig() { const form = Form.useFormInstance(); const task_type = Form.useWatch('task_type', form); + const metrics = Form.useWatch('metrics', form) || []; + const selectedMetrics = metrics + .map((item: { name: string; value: number }) => item?.name) + .filter(Boolean); + const allMetricsOptions = + task_type === AutoMLTaskType.Classification ? classificationMetrics : regressionMetrics; + const metricsOptions = allMetricsOptions.filter((item) => !selectedMetrics.includes(item.label)); + return ( <> - @@ -30,9 +37,9 @@ function TrialConfig() { {(fields, { add, remove }) => ( <> {fields.map(({ key, name, ...restField }, index) => ( - + - : + : -
+ )} -
+
))} {fields.length === 0 && ( diff --git a/react-ui/src/pages/AutoML/components/CreateForm/index.less b/react-ui/src/pages/AutoML/components/CreateForm/index.less index 8e8414a5..5ee9aacc 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/index.less +++ b/react-ui/src/pages/AutoML/components/CreateForm/index.less @@ -6,136 +6,136 @@ } } -.command { - width: 83.33%; - margin-bottom: 20px; - border: 1px solid rgba(234, 234, 234, 0.8); - border-radius: 4px; - &__header { - height: 50px; - padding-left: 8px; - color: @text-color; - font-size: @font-size; - background: #f8f8f9; - border-radius: 4px 4px 0px 0px; - - &__name { - flex: none; - width: 100px; - } - &__command { - flex: 1; - margin-right: 15px; - } - - &__operation { - flex: none; - width: 100px; - } - } - &__body { - padding: 8px; - border-bottom: 1px solid rgba(234, 234, 234, 0.8); - - &:last-child { - border-bottom: none; - } - - &__name { - flex: none; - width: 100px; - } - - &__command { - flex: 1; - margin-right: 15px; - margin-bottom: 0 !important; - } - - &__operation { - flex: none; - width: 100px; - } - } - - &__add { - display: flex; - align-items: center; - justify-content: center; - padding: 15px 0; - } -} - -.hyper-parameter { - width: 83.33%; - margin-bottom: 20px; - border: 1px solid rgba(234, 234, 234, 0.8); - border-radius: 4px; - &__header { - height: 50px; - padding-left: 8px; - color: @text-color; - font-size: @font-size; - background: #f8f8f9; - border-radius: 4px 4px 0px 0px; - - &__name, - &__type, - &__space { - flex: 1; - margin-right: 15px; - } - - &__operation { - flex: none; - width: 100px; - } - } - &__body { - padding: 8px; - border-bottom: 1px solid rgba(234, 234, 234, 0.8); - - &:last-child { - border-bottom: none; - } - - &__name, - &__type, - &__space { - flex: 1; - margin-right: 15px; - margin-bottom: 0 !important; - } - - &__operation { - flex: none; - width: 100px; - } - } - - &__add { - display: flex; - align-items: center; - justify-content: center; - padding: 15px 0; - } -} - -.trial-metrics { - width: calc(41.67% - 6px); - margin-bottom: 20px; - padding: 0 20px; - border: 1px dashed #e0e0e0; - border-radius: 8px; -} - -.upload-tip { - margin-top: 5px; - color: @text-color-secondary; - font-size: 14px; -} - -.upload-button { - height: 46px; - font-size: 15px; -} +// .command { +// width: 83.33%; +// margin-bottom: 20px; +// border: 1px solid rgba(234, 234, 234, 0.8); +// border-radius: 4px; +// &__header { +// height: 50px; +// padding-left: 8px; +// color: @text-color; +// font-size: @font-size; +// background: #f8f8f9; +// border-radius: 4px 4px 0px 0px; + +// &__name { +// flex: none; +// width: 100px; +// } +// &__command { +// flex: 1; +// margin-right: 15px; +// } + +// &__operation { +// flex: none; +// width: 100px; +// } +// } +// &__body { +// padding: 8px; +// border-bottom: 1px solid rgba(234, 234, 234, 0.8); + +// &:last-child { +// border-bottom: none; +// } + +// &__name { +// flex: none; +// width: 100px; +// } + +// &__command { +// flex: 1; +// margin-right: 15px; +// margin-bottom: 0 !important; +// } + +// &__operation { +// flex: none; +// width: 100px; +// } +// } + +// &__add { +// display: flex; +// align-items: center; +// justify-content: center; +// padding: 15px 0; +// } +// } + +// .hyper-parameter { +// width: 83.33%; +// margin-bottom: 20px; +// border: 1px solid rgba(234, 234, 234, 0.8); +// border-radius: 4px; +// &__header { +// height: 50px; +// padding-left: 8px; +// color: @text-color; +// font-size: @font-size; +// background: #f8f8f9; +// border-radius: 4px 4px 0px 0px; + +// &__name, +// &__type, +// &__space { +// flex: 1; +// margin-right: 15px; +// } + +// &__operation { +// flex: none; +// width: 100px; +// } +// } +// &__body { +// padding: 8px; +// border-bottom: 1px solid rgba(234, 234, 234, 0.8); + +// &:last-child { +// border-bottom: none; +// } + +// &__name, +// &__type, +// &__space { +// flex: 1; +// margin-right: 15px; +// margin-bottom: 0 !important; +// } + +// &__operation { +// flex: none; +// width: 100px; +// } +// } + +// &__add { +// display: flex; +// align-items: center; +// justify-content: center; +// padding: 15px 0; +// } +// } + +// .trial-metrics { +// width: calc(41.67% - 6px); +// margin-bottom: 20px; +// padding: 0 20px; +// border: 1px dashed #e0e0e0; +// border-radius: 8px; +// } + +// .upload-tip { +// margin-top: 5px; +// color: @text-color-secondary; +// font-size: 14px; +// } + +// .upload-button { +// height: 46px; +// font-size: 15px; +// } diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx index b1b9ae29..c2af2074 100644 --- a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx @@ -6,18 +6,19 @@ import styles from './index.less'; -function ExecuteScheduleCell(status?: any) { +function ExecuteScheduleCell(progress?: number) { + const width = (progress || 0) * 100 + '%'; return (
- + {/* 1/2 - + */}
); } diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts index 59496c64..bd61f941 100644 --- a/react-ui/src/pages/AutoML/types.ts +++ b/react-ui/src/pages/AutoML/types.ts @@ -51,4 +51,7 @@ export type AutoMLData = { exclude_feature_preprocessor?: string; exclude_regressor?: string; dataset?: string; -}; +} & Omit< + FormData, + 'metrics|dataset|include_classifier|include_feature_preprocessor|include_regressor|exclude_classifier|exclude_feature_preprocessor|exclude_regressor' +>; diff --git a/react-ui/src/utils/sessionStorage.ts b/react-ui/src/utils/sessionStorage.ts index 8ffa8836..b71a35fd 100644 --- a/react-ui/src/utils/sessionStorage.ts +++ b/react-ui/src/utils/sessionStorage.ts @@ -11,6 +11,8 @@ export default class SessionStorage { static readonly editorUrlKey = 'editor-url'; // 客户端信息 static readonly clientInfoKey = 'client-info'; + // 自动机器学习记录ID + static readonly autoMLRecordIDKey = 'auto-ml-record-id'; static getItem(key: string, isObject: boolean = false) { const jsonStr = sessionStorage.getItem(key); From db0c9abc9e7541c6e652093f61a30ec55694778e Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 27 Nov 2024 08:38:39 +0800 Subject: [PATCH 17/24] =?UTF-8?q?feat:=20=E6=9C=BA=E5=99=A8=E5=AD=A6?= =?UTF-8?q?=E4=B9=A0=E8=AE=AD=E7=BB=83=E9=9B=86=E6=AF=94=E7=8E=87=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/AutoML/components/CreateForm/ExecuteConfig.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx index 99acd712..87d5ad3f 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx @@ -397,7 +397,11 @@ function ExecuteConfig() {
- + @@ -408,7 +412,7 @@ function ExecuteConfig() { From 605a0614e9f203e0e6e8c60f7eff560be5ec3e8f Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Fri, 29 Nov 2024 13:55:33 +0800 Subject: [PATCH 18/24] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/assets/img/dataset-config-icon.png | Bin 0 -> 3407 bytes .../src/components/BasicInfo/components.tsx | 113 ++++++ react-ui/src/components/BasicInfo/format.ts | 48 +++ react-ui/src/components/BasicInfo/index.tsx | 122 +------ react-ui/src/components/BasicInfo/types.ts | 14 + .../src/components/BasicTableInfo/index.tsx | 4 +- react-ui/src/enums/index.ts | 12 +- react-ui/src/pages/AutoML/Create/index.less | 2 +- react-ui/src/pages/AutoML/Create/index.tsx | 33 +- react-ui/src/pages/AutoML/List/index.tsx | 49 ++- .../AutoML/components/AutoMLBasic/index.less | 6 + .../AutoML/components/AutoMLBasic/index.tsx | 341 +++++++++++++++--- .../AutoML/components/ConfigInfo/index.tsx | 3 +- .../components/CreateForm/DatasetConfig.tsx | 2 +- .../components/CreateForm/ExecuteConfig.tsx | 27 +- .../components/CreateForm/TrialConfig.tsx | 11 +- .../AutoML/components/CreateForm/index.less | 15 +- react-ui/src/pages/AutoML/types.ts | 50 +-- react-ui/src/services/autoML.js | 2 +- react-ui/src/utils/index.ts | 12 +- 20 files changed, 616 insertions(+), 250 deletions(-) create mode 100644 react-ui/src/assets/img/dataset-config-icon.png create mode 100644 react-ui/src/components/BasicInfo/components.tsx create mode 100644 react-ui/src/components/BasicInfo/format.ts create mode 100644 react-ui/src/components/BasicInfo/types.ts diff --git a/react-ui/src/assets/img/dataset-config-icon.png b/react-ui/src/assets/img/dataset-config-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1afc4f729b44e9b6c0fc1b6cc4ddc39cd8d58672 GIT binary patch literal 3407 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 + {formatValue.map((item: BasicInfoLink) => ( + + ))} + + ); + } else if (React.isValidElement(formatValue)) { + // 这个判断必须在下面的判断之前 + valueComponent = ( + + ); + } else if (typeof formatValue === 'object' && formatValue) { + valueComponent = ( + + ); + } else { + valueComponent = ( + + ); + } + return ( +
+
+ {label} +
+ {valueComponent} +
+ ); +} + +type BasicInfoItemValueProps = { + ellipsis?: boolean; + classPrefix: string; + value: string | React.ReactNode; + link?: string; + url?: string; +}; + +export function BasicInfoItemValue({ + value, + link, + url, + ellipsis, + classPrefix, +}: BasicInfoItemValueProps) { + const myClassName = `${classPrefix}__item__value`; + let component = undefined; + if (url && value) { + component = ( + + {value} + + ); + } else if (link && value) { + component = ( + + {value} + + ); + } else if (React.isValidElement(value)) { + return value; + } else { + component = {value ?? '--'}; + } + + return ( +
+ + {component} + +
+ ); +} diff --git a/react-ui/src/components/BasicInfo/format.ts b/react-ui/src/components/BasicInfo/format.ts new file mode 100644 index 00000000..0dae2422 --- /dev/null +++ b/react-ui/src/components/BasicInfo/format.ts @@ -0,0 +1,48 @@ +/* + * @Author: 赵伟 + * @Date: 2024-11-29 09:27:19 + * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的常用转化格式 + */ + +// 格式化日期 +export { formatDate } from '@/utils/date'; + +/** + * 格式化字符串数组 + * @param value - 字符串数组 + * @returns 逗号分隔的字符串 + */ +export const formatList = (value: string[] | null | undefined): string => { + if ( + value === undefined || + value === null || + Array.isArray(value) === false || + value.length === 0 + ) { + return '--'; + } + return value.join(','); +}; + +/** + * 格式化布尔值 + * @param value - 布尔值 + * @returns "是" 或 "否" + */ +export const formatBoolean = (value: boolean): string => { + return value ? '是' : '否'; +}; + +type FormatEnum = (value: string | number) => string; + +/** + * 格式化枚举 + * @param options - 枚举选项 + * @returns 格式化枚举函数 + */ +export const formatEnum = (options: { value: string | number; label: string }[]): FormatEnum => { + return (value: string | number) => { + const option = options.find((item) => item.value === value); + return option ? option.label : '--'; + }; +}; diff --git a/react-ui/src/components/BasicInfo/index.tsx b/react-ui/src/components/BasicInfo/index.tsx index 1fa18783..1336d0b6 100644 --- a/react-ui/src/components/BasicInfo/index.tsx +++ b/react-ui/src/components/BasicInfo/index.tsx @@ -1,21 +1,10 @@ -import { Link } from '@umijs/max'; -import { Typography } from 'antd'; import classNames from 'classnames'; import React from 'react'; +import { BasicInfoItem } from './components'; import './index.less'; - -export type BasicInfoLink = { - value: string; - link?: string; - url?: string; -}; - -export type BasicInfoData = { - label: string; - value?: any; - ellipsis?: boolean; - format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; -}; +import type { BasicInfoData, BasicInfoLink } from './types'; +export * from './format'; +export type { BasicInfoData, BasicInfoLink }; type BasicInfoProps = { datas: BasicInfoData[]; @@ -24,20 +13,6 @@ type BasicInfoProps = { labelWidth: number; }; -type BasicInfoItemProps = { - data: BasicInfoData; - labelWidth: number; - classPrefix: string; -}; - -type BasicInfoItemValueProps = { - ellipsis?: boolean; - classPrefix: string; - value: string | React.ReactNode; - link?: string; - url?: string; -}; - export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { return (
@@ -52,92 +27,3 @@ export default function BasicInfo({ datas, className, style, labelWidth }: Basic
); } - -export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) { - const { label, value, format, ellipsis } = data; - const formatValue = format ? format(value) : value; - const myClassName = `${classPrefix}__item`; - let valueComponent = undefined; - if (Array.isArray(formatValue)) { - valueComponent = ( -
- {formatValue.map((item: BasicInfoLink) => ( - - ))} -
- ); - } else if (React.isValidElement(formatValue)) { - // 这个判断必须在下面的判断之前 - valueComponent = ( - - ); - } else if (typeof formatValue === 'object' && formatValue) { - valueComponent = ( - - ); - } else { - valueComponent = ( - - ); - } - return ( -
-
- {label} -
- {valueComponent} -
- ); -} - -export function BasicInfoItemValue({ - value, - link, - url, - ellipsis, - classPrefix, -}: BasicInfoItemValueProps) { - const myClassName = `${classPrefix}__item__value`; - let component = undefined; - if (url && value) { - component = ( - - {value} - - ); - } else if (link && value) { - component = ( - - {value} - - ); - } else if (React.isValidElement(value)) { - return value; - } else { - component = {value ?? '--'}; - } - - return ( -
- - {component} - -
- ); -} diff --git a/react-ui/src/components/BasicInfo/types.ts b/react-ui/src/components/BasicInfo/types.ts new file mode 100644 index 00000000..a7c10ba0 --- /dev/null +++ b/react-ui/src/components/BasicInfo/types.ts @@ -0,0 +1,14 @@ +// 基础信息 +export type BasicInfoData = { + label: string; + value?: any; + ellipsis?: boolean; + format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; +}; + +// 值为链接的类型 +export type BasicInfoLink = { + value: string; + link?: string; + url?: string; +}; diff --git a/react-ui/src/components/BasicTableInfo/index.tsx b/react-ui/src/components/BasicTableInfo/index.tsx index df167ae2..104bc2bb 100644 --- a/react-ui/src/components/BasicTableInfo/index.tsx +++ b/react-ui/src/components/BasicTableInfo/index.tsx @@ -1,6 +1,8 @@ import classNames from 'classnames'; -import { BasicInfoItem, type BasicInfoData, type BasicInfoLink } from '../BasicInfo'; +import { BasicInfoItem } from '../BasicInfo/components'; +import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types'; import './index.less'; +export * from '../BasicInfo/format'; export type { BasicInfoData, BasicInfoLink }; type BasicTableInfoProps = { diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index 05e14b34..26359678 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -92,19 +92,29 @@ export enum AutoMLTaskType { Regression = 'regression', } +export const autoMLTaskTypeOptions = [ + { label: '分类', value: AutoMLTaskType.Classification }, + { label: '回归', value: AutoMLTaskType.Regression }, +]; + // 自动化任务集成策略 export enum AutoMLEnsembleClass { Default = 'default', SingleBest = 'SingleBest', } +export const autoMLEnsembleClassOptions = [ + { label: '集成模型', value: AutoMLEnsembleClass.Default }, + { label: '单一最佳模型', value: AutoMLEnsembleClass.SingleBest }, +]; + // 自动化任务重采样策略 export enum AutoMLResamplingStrategy { Holdout = 'holdout', CrossValid = 'crossValid', } -export const resamplingStrategyOptions = [ +export const autoMLResamplingStrategyOptions = [ { label: 'holdout', value: AutoMLResamplingStrategy.Holdout }, { label: 'crossValid', value: AutoMLResamplingStrategy.CrossValid }, ]; diff --git a/react-ui/src/pages/AutoML/Create/index.less b/react-ui/src/pages/AutoML/Create/index.less index 7c88312e..f8d15d2e 100644 --- a/react-ui/src/pages/AutoML/Create/index.less +++ b/react-ui/src/pages/AutoML/Create/index.less @@ -1,4 +1,4 @@ -.create-service-version { +.create-automl { height: 100%; &__content { diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 26e1866c..4d4d70d3 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -5,9 +5,9 @@ */ import PageTitle from '@/components/PageTitle'; -import { AutoMLTaskType } from '@/enums'; -import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/autoML'; -import { parseJsonText, trimCharacter } from '@/utils'; +import { AutoMLEnsembleClass, AutoMLTaskType } from '@/enums'; +import { addAutoMLReq, getAutoMLInfoReq, updateAutoMLReq } from '@/services/autoML'; +import { convertEmptyStringToUndefined, parseJsonText, trimCharacter } from '@/utils'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; import SessionStorage from '@/utils/sessionStorage'; @@ -49,7 +49,7 @@ function CreateAutoML() { // 获取服务详情 const getAutoMLInfo = async (id: number, isCopy = false) => { - const [res] = await to(getDatasetInfoReq({ id })); + const [res] = await to(getAutoMLInfoReq({ id })); if (res && res.data) { const autoMLInfo: AutoMLData = res.data; const { @@ -96,7 +96,7 @@ function CreateAutoML() { } }; - // 创建版本 + // 创建、更新、复制实验 const createExperiment = async (formData: FormData) => { const include_classifier = formData['include_classifier']?.join(','); const include_feature_preprocessor = formData['include_feature_preprocessor']?.join(','); @@ -113,12 +113,12 @@ function CreateAutoML() { // 根据后台要求,修改表单数据 const object = { ...omit(formData), - include_classifier, - include_feature_preprocessor, - include_regressor, - exclude_classifier, - exclude_feature_preprocessor, - exclude_regressor, + include_classifier: convertEmptyStringToUndefined(include_classifier), + include_feature_preprocessor: convertEmptyStringToUndefined(include_feature_preprocessor), + include_regressor: convertEmptyStringToUndefined(include_regressor), + exclude_classifier: convertEmptyStringToUndefined(exclude_classifier), + exclude_feature_preprocessor: convertEmptyStringToUndefined(exclude_feature_preprocessor), + exclude_regressor: convertEmptyStringToUndefined(exclude_regressor), metrics: metrics ? JSON.stringify(metrics) : undefined, target_columns, }; @@ -148,7 +148,6 @@ function CreateAutoML() { navigate(-1); }; - const disabled = id !== null || id !== undefined; let buttonText = '新建'; let title = '新增实验'; if (id) { @@ -157,26 +156,28 @@ function CreateAutoML() { } return ( -
+
-
+
- + diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx index be76e591..34a5d395 100644 --- a/react-ui/src/pages/AutoML/List/index.tsx +++ b/react-ui/src/pages/AutoML/List/index.tsx @@ -6,7 +6,7 @@ import KFIcon from '@/components/KFIcon'; import PageTitle from '@/components/PageTitle'; import { useCacheState } from '@/hooks/pageCacheState'; -import { deleteAutoMLReq, getAutoMLListReq } from '@/services/autoML'; +import { deleteAutoMLReq, getAutoMLListReq, runAutoMLReq } from '@/services/autoML'; import themes from '@/styles/theme.less'; import { to } from '@/utils/promise'; import SessionStorage from '@/utils/sessionStorage'; @@ -129,6 +129,24 @@ function AutoMLList() { navigate(`/pipeline/autoML/info/${record.id}`); }; + // 启动 + const startAutoML = async (record: AutoMLData) => { + const [res] = await to(runAutoMLReq(record.id)); + if (res) { + message.success('操作成功'); + getServiceList(); + } + }; + + // 停止 + const stopAutoML = async (record: AutoMLData) => { + const [res] = await to(runAutoMLReq(record.id)); + if (res) { + message.success('操作成功'); + getServiceList(); + } + }; + // 分页切换 const handleTableChange: TableProps['onChange'] = ( pagination, @@ -202,13 +220,10 @@ function AutoMLList() { { title: '操作', dataIndex: 'operation', - width: 400, + width: 320, key: 'operation', render: (_: any, record: AutoMLData) => (
- - + {record.run_state === 'Running' ? ( + + ) : ( + + )} { + if (!dataset || !dataset.name || !dataset.version) { + return '--'; + } + return `${dataset.name}:${dataset.version}`; +}; + +// 格式化优化方向 +const formatOptimizeMode = (value: boolean) => { + return value ? '越大越好' : '越小越好'; +}; + +const formatMetricsWeight = (value: string) => { + if (!value) { + return '--'; + } + const json = parseJsonText(value); + if (!json) { + return '--'; + } + return Object.entries(json) + .map(([key, value]) => `${key}:${value}`) + .join('\n'); +}; + function AutoMLBasic() { - useEffect(() => {}, []); + const params = useParams(); + const id = safeInvoke(Number)(params.id); + const [basicDatas, setBasicDatas] = useState([]); + const [configDatas, setConfigDatas] = useState([]); + + useEffect(() => { + if (id) { + getAutoMLInfo(id); + } + }, []); - const datas: BasicInfoData[] = [ - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: '测试项目名称', - ellipsis: true, - }, - { - label: '项目名称', - value: , - ellipsis: false, - }, - ]; + // const basicDatas: BasicInfoData[] = [ + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: '测试项目名称', + // ellipsis: true, + // }, + // { + // label: '项目名称', + // value: , + // ellipsis: false, + // }, + // ]; + + // 获取服务详情 + const getAutoMLInfo = async (id: number) => { + const [res] = await to(getAutoMLInfoReq({ id })); + if (res && res.data) { + const info: AutoMLData = res.data; + const basicDatas: BasicInfoData[] = [ + { + label: '实验名称', + value: info.ml_name, + ellipsis: true, + }, + { + label: '实验描述', + value: info.ml_description, + ellipsis: true, + }, + { + label: '创建人', + value: info.create_by, + ellipsis: true, + }, + { + label: '创建时间', + value: info.create_time, + ellipsis: true, + format: formatDate, + }, + { + label: '更新时间', + value: info.update_time, + ellipsis: true, + format: formatDate, + }, + { + label: '状态', + value: info.run_state, + ellipsis: true, + }, + ]; + setBasicDatas(basicDatas); + + const configDatas: BasicInfoData[] = [ + { + label: '任务类型', + value: info.task_type, + ellipsis: true, + format: formatEnum(autoMLTaskTypeOptions), + }, + { + label: '特征预处理算法', + value: info.include_feature_preprocessor, + ellipsis: true, + }, + { + label: '排除的特征预处理算法', + value: info.exclude_feature_preprocessor, + ellipsis: true, + }, + { + label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', + value: + info.task_type === AutoMLTaskType.Regression + ? info.include_regressor + : info.include_classifier, + ellipsis: true, + }, + { + label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', + value: + info.task_type === AutoMLTaskType.Regression + ? info.exclude_regressor + : info.exclude_classifier, + ellipsis: true, + }, + { + label: '集成方式', + value: info.ensemble_class, + ellipsis: true, + format: formatEnum(autoMLEnsembleClassOptions), + }, + { + label: '集成模型数量', + value: info.ensemble_size, + ellipsis: true, + }, + { + label: '集成最佳模型数量', + value: info.ensemble_nbest, + ellipsis: true, + }, + { + label: '最大数量', + value: info.max_models_on_disc, + ellipsis: true, + }, + { + label: '内存限制(MB)', + value: info.memory_limit, + ellipsis: true, + }, + { + label: '时间限制(秒)', + value: info.per_run_time_limit, + ellipsis: true, + }, + { + label: '搜索时间限制(秒)', + value: info.time_left_for_this_task, + ellipsis: true, + }, + { + label: '重采样策略', + value: info.resampling_strategy, + ellipsis: true, + }, + { + label: '交叉验证折数', + value: info.folds, + ellipsis: true, + }, + { + label: '是否打乱', + value: info.shuffle, + ellipsis: true, + format: formatBoolean, + }, + { + label: '训练集比率', + value: info.train_size, + ellipsis: true, + }, + { + label: '测试集比率', + value: info.test_size, + ellipsis: true, + }, + { + label: '计算指标', + value: info.scoring_functions, + ellipsis: true, + }, + { + label: '随机种子', + value: info.seed, + ellipsis: true, + }, + + { + label: '数据集', + value: info.dataset, + ellipsis: true, + format: formatDataset, + }, + { + label: '预测目标列', + value: info.target_columns, + ellipsis: true, + }, + { + label: '指标名称', + value: info.metric_name, + ellipsis: true, + }, + { + label: '优化方向', + value: info.greater_is_better, + ellipsis: true, + format: formatOptimizeMode, + }, + { + label: '指标权重', + value: info.metrics, + ellipsis: true, + format: formatMetricsWeight, + }, + ]; + setConfigDatas(configDatas); + } + }; return (
- - + {/* - + /> */}
); diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx index e12b291b..2fbb6825 100644 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx +++ b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx @@ -3,7 +3,8 @@ import classNames from 'classnames'; import { useEffect } from 'react'; import ConfigTitle from '../ConfigTitle'; import styles from './index.less'; -export { type BasicInfoData }; +export * from '@/components/BasicInfo/format'; +export type { BasicInfoData }; type ConfigInfoProps = { title: string; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx index 79984a41..b3b3f2dd 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/DatasetConfig.tsx @@ -10,7 +10,7 @@ function DatasetConfig() { <> diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx index 87d5ad3f..b472ddb3 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx @@ -3,9 +3,11 @@ import { AutoMLEnsembleClass, AutoMLResamplingStrategy, AutoMLTaskType, - resamplingStrategyOptions, + autoMLEnsembleClassOptions, + autoMLResamplingStrategyOptions, + autoMLTaskTypeOptions, } from '@/enums'; -import { Col, Form, Input, InputNumber, Radio, Row, Select, Switch } from 'antd'; +import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd'; // 分类算法 const classificationAlgorithms = [ @@ -120,10 +122,10 @@ function ExecuteConfig() { name="task_type" rules={[{ required: true, message: '请选择任务类型' }]} > - form.resetFields(['metrics'])}> - 分类 - 回归 - + form.resetFields(['metrics'])} + > @@ -259,10 +261,7 @@ function ExecuteConfig() { name="ensemble_class" tooltip="仅使用单个最佳模型还是集成模型" > - - 集成模型 - 单一最佳模型 - + @@ -357,7 +356,7 @@ function ExecuteConfig() {
+ + + + ); +} + +export default ExperimentHistory; diff --git a/react-ui/src/pages/AutoML/components/ExperimentLog/index.less b/react-ui/src/pages/AutoML/components/ExperimentLog/index.less new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.less b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less new file mode 100644 index 00000000..bdf1858c --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.less @@ -0,0 +1,18 @@ +.experiment-result { + height: 100%; + margin-top: 10px; + padding: 20px @content-padding 0; + overflow-y: auto; + background-color: white; + border-radius: 10px; + + &__text { + margin-bottom: 20px; + white-space: pre-wrap; + } + + &__image { + display: block; + height: 200px; + } +} diff --git a/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx new file mode 100644 index 00000000..2e0b71a6 --- /dev/null +++ b/react-ui/src/pages/AutoML/components/ExperimentResult/index.tsx @@ -0,0 +1,51 @@ +import { getFileReq } from '@/services/file'; +import { to } from '@/utils/promise'; +import { useEffect, useMemo, useState } from 'react'; +import styles from './index.less'; + +type ExperimentResultProps = { + fileUrl?: string; + imageUrl?: string; +}; + +function ExperimentResult({ fileUrl, imageUrl }: ExperimentResultProps) { + const [result, setResult] = useState(''); + + const images = useMemo(() => { + if (imageUrl) { + return imageUrl.split(',').map((item) => item.trim()); + } + return []; + }, [imageUrl]); + + useEffect(() => { + if (fileUrl) { + getResultFile(); + } + }, [fileUrl]); + + // 获取实验运行历史记录 + const getResultFile = async () => { + const [res] = await to(getFileReq(fileUrl)); + if (res) { + setResult(res as any as string); + } + }; + + return ( +
+
{result}
+ {images.map((item, index) => ( + + ))} +
+ ); +} + +export default ExperimentResult; diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts index 3464e599..f068d168 100644 --- a/react-ui/src/pages/AutoML/types.ts +++ b/react-ui/src/pages/AutoML/types.ts @@ -61,6 +61,24 @@ export type AutoMLData = { 'metrics|dataset|include_classifier|include_feature_preprocessor|include_regressor|exclude_classifier|exclude_feature_preprocessor|exclude_regressor' >; -export type ExperimentInstanceData = { +// 自动机器学习实验实例 +export type AutoMLInstanceData = { id: number; + auto_ml_id: number; + result_path: string; + model_path: string; + img_path: string; + run_history_path: string; + state: number; + status: string; + node_status: string; + node_result: string; + param: string; + source: string | null; + argo_ins_name: string; + argo_ins_ns: string; + create_time: string; + update_time: string; + finish_time: string; + nodeStatus?: { [key: string]: string }; }; diff --git a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx index b9a93e8f..27f3354c 100644 --- a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx @@ -135,7 +135,7 @@ function LogGroup({ const setupSockect = () => { let { host } = location; if (process.env.NODE_ENV === 'development') { - host = '172.20.32.185:31213'; + host = '172.20.32.181:31213'; } const socket = new WebSocket( `ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`, diff --git a/react-ui/src/pages/Experiment/index.jsx b/react-ui/src/pages/Experiment/index.jsx index 95bc2953..998fb436 100644 --- a/react-ui/src/pages/Experiment/index.jsx +++ b/react-ui/src/pages/Experiment/index.jsx @@ -271,6 +271,7 @@ function Experiment() { const [res] = await to(runExperiments(id)); if (res) { message.success('运行成功'); + refreshExperimentList(); refreshExperimentIns(id); } else { message.error('运行失败'); diff --git a/react-ui/src/requestConfig.ts b/react-ui/src/requestConfig.ts index b268bc47..6d298d33 100644 --- a/react-ui/src/requestConfig.ts +++ b/react-ui/src/requestConfig.ts @@ -58,11 +58,12 @@ export const requestConfig: RequestConfig = { const options = config as RequestOptions; const skipErrorHandler = options?.skipErrorHandler; const skipLoading = options?.skipLoading; + const skipValidating = options?.skipValidating; if (!skipLoading) { Loading.hide(); } if (status >= 200 && status < 300) { - if (data && (data instanceof Blob || data.code === 200)) { + if (data && (skipValidating || data instanceof Blob || data.code === 200)) { return response; } else if (data && data.code === 401) { clearSessionToken(); diff --git a/react-ui/src/services/autoML.js b/react-ui/src/services/autoML/index.js similarity index 100% rename from react-ui/src/services/autoML.js rename to react-ui/src/services/autoML/index.js diff --git a/react-ui/src/services/file/index.js b/react-ui/src/services/file/index.js new file mode 100644 index 00000000..a6786007 --- /dev/null +++ b/react-ui/src/services/file/index.js @@ -0,0 +1,20 @@ +/* + * @Author: 赵伟 + * @Date: 2024-11-30 11:43:26 + * @Description: 请求文件,比如 json 文件 + */ + + +import { request } from '@umijs/max'; + +// 获取文件,不需要token,非结构化数据 +export function getFileReq(url, config) { + return request(url, { + method: 'GET', + headers: { + isToken: false, + }, + skipValidating: true, + ...config + }); +} \ No newline at end of file From 1b70a295f4789350edf4bfee664ccd0bb74cf9d4 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 2 Dec 2024 16:02:24 +0800 Subject: [PATCH 21/24] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9C=BA=E5=99=A8=E5=AD=A6=E4=B9=A0=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/app.tsx | 8 +- react-ui/src/assets/img/resample-icon.png | Bin 0 -> 1281 bytes react-ui/src/enums/index.ts | 2 +- react-ui/src/global.less | 2 +- react-ui/src/iconfont/iconfont.js | 2 +- react-ui/src/pages/AutoML/Create/index.tsx | 23 ++- react-ui/src/pages/AutoML/Info/index.tsx | 17 +- react-ui/src/pages/AutoML/Instance/index.less | 41 ++++- react-ui/src/pages/AutoML/Instance/index.tsx | 148 ++++++++++++++---- .../AutoML/components/AutoMLBasic/index.tsx | 67 +++++++- .../AutoML/components/ConfigInfo/index.less | 4 +- .../AutoML/components/ConfigTitle/index.less | 1 + .../components/CreateForm/ExecuteConfig.tsx | 88 ++++++----- .../AutoML/components/CreateForm/index.less | 136 +--------------- .../components/ExperimentHistory/index.less | 6 +- .../components/ExperimentResult/index.less | 33 +++- .../components/ExperimentResult/index.tsx | 23 +-- react-ui/src/pages/AutoML/types.ts | 3 +- .../components/ExperimentDrawer/index.less | 6 + .../components/ExperimentDrawer/index.tsx | 18 ++- .../Experiment/components/LogGroup/index.tsx | 2 +- .../Experiment/components/LogList/index.less | 6 +- react-ui/src/types.ts | 10 ++ 23 files changed, 368 insertions(+), 278 deletions(-) create mode 100644 react-ui/src/assets/img/resample-icon.png diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 7af61ab5..2f757e15 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -50,7 +50,7 @@ export async function getInitialState(): Promise { // 如果不是登录页面,执行 const { location } = history; - console.log('getInitialState', needAuth(location.pathname)); + // console.log('getInitialState', needAuth(location.pathname)); if (needAuth(location.pathname)) { const currentUser = await fetchUserInfo(); return { @@ -163,7 +163,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { const { location } = e; const menus = getRemoteMenu(); - console.log('onRouteChange', menus); + // console.log('onRouteChange', menus); if (menus === null && needAuth(location.pathname)) { history.go(0); } @@ -174,12 +174,12 @@ export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { }; export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { - console.log('patchClientRoutes', e); + // console.log('patchClientRoutes', e); patchRouteWithRemoteMenus(e.routes); }; export function render(oldRender: () => void) { - console.log('render'); + // console.log('render'); const token = getAccessToken(); if (!token || token?.length === 0) { oldRender(); diff --git a/react-ui/src/assets/img/resample-icon.png b/react-ui/src/assets/img/resample-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa24c1aa9c82738a5a6907422b8fca983cbd723a GIT binary patch literal 1281 zcmV+c1^)VpP)>GQg}T3ov9*Z}=SlJ2Aj8 zJqn-aJ5yjXBA`V8**zm2vQWeO!43;jx=bjl#c*Wm^p%tphg@G;R!I7>zlC>?OJ;;a z?-~FDK$ed4`D6et95-lr>}$T|CR=m@fCFnuP;vtUs9Kg_8zzA$+icLVn^TafLN?VV zJcRWZ(=v`l-$&2$595-VQAPzVhLcACN(h5C8Hlv2V}jP<{vrIuz+ep{G9^U-Q(($4 zGw6+dD8XwWjH~Fe@+~s}`3jW%5gPv~7;rb6R9&_`BL#pk?E!!Yh@sJNXldDkQEcTn zw303ANHSbGmsy&6(K8ECZnw%F41jDUNgDv{Dtf2AY|AV$L^Dl`+6;s0grHs!!na_x zFS9qz;bDpnwDDc92>`F}t$5am^&LGu_S;@^z6MR*2_PZpErX~6Byd0^=!EITW_jLc zls(L64Y3%tN27LhY+#HTe+;hv^9wH}=!MbajHG6i*BnIi2q^hla;*yi;Kap=KBi9k zjvY_^nh*3c!4ADAd>>SGP??{Z^45G9${m{-#5{$=H z-6U(#d5v>0;yp@6R-f=RYGW?_d)ZB2KS8{D%K`b87C~&@Sk4%?!X?x_{qR8?WmX zOlDH5XMZk_VP8=ZkHhePpqDQEUjrkG)#d}p0O)yrxP`A2+T1$wfr3#6k%6NC^cjF; zmct;LJ|=J<<@Jk>V}4`;2cw**YLfL9(`saA2}&k`jQkY2pbjulgc(=rlgauV4gUhB zv3}BcesGR`CKsFeiK1GAhPMIs^#az9{gXx!!HgV;>j28{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file diff --git a/react-ui/src/pages/AutoML/Create/index.tsx b/react-ui/src/pages/AutoML/Create/index.tsx index 4d4d70d3..1dc2c3cc 100644 --- a/react-ui/src/pages/AutoML/Create/index.tsx +++ b/react-ui/src/pages/AutoML/Create/index.tsx @@ -104,10 +104,15 @@ function CreateAutoML() { const exclude_classifier = formData['exclude_classifier']?.join(','); const exclude_feature_preprocessor = formData['exclude_feature_preprocessor']?.join(','); const exclude_regressor = formData['exclude_regressor']?.join(','); - const metrics = formData['metrics']?.reduce((acc, cur) => { - acc[cur.name] = cur.value; - return acc; - }, {} as Record); + const formMetrics = formData['metrics']; + const metrics = + formMetrics && Array.isArray(formMetrics) && formMetrics.length > 0 + ? formMetrics.reduce((acc, cur) => { + acc[cur.name] = cur.value; + return acc; + }, {} as Record) + : undefined; + const target_columns = trimCharacter(formData['target_columns'], ','); // 根据后台要求,修改表单数据 @@ -174,6 +179,16 @@ function CreateAutoML() { shuffle: false, ensemble_class: AutoMLEnsembleClass.Default, greater_is_better: true, + ensemble_size: 50, + ensemble_nbest: 50, + max_models_on_disc: 50, + memory_limit: 3072, + per_run_time_limit: 600, + time_left_for_this_task: 3600, + resampling_strategy: 'holdout', + test_size: 0.25, + train_size: 0.67, + seed: 1, }} > diff --git a/react-ui/src/pages/AutoML/Info/index.tsx b/react-ui/src/pages/AutoML/Info/index.tsx index caa2cc55..cc5247e2 100644 --- a/react-ui/src/pages/AutoML/Info/index.tsx +++ b/react-ui/src/pages/AutoML/Info/index.tsx @@ -4,16 +4,14 @@ * @Description: 自主机器学习详情 */ import KFIcon from '@/components/KFIcon'; +import PageTitle from '@/components/PageTitle'; import { CommonTabKeys } from '@/enums'; import { getAutoMLInfoReq } from '@/services/autoML'; -import themes from '@/styles/theme.less'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; import { useParams } from '@umijs/max'; -import { Tabs } from 'antd'; import { useEffect, useState } from 'react'; import AutoMLBasic from '../components/AutoMLBasic'; -import AutoMLTable from '../components/AutoMLTable'; import { AutoMLData } from '../types'; import styles from './index.less'; @@ -52,19 +50,10 @@ function AutoMLInfo() { return (
-
- -
+
- {activeTab === CommonTabKeys.Public && } - {activeTab === CommonTabKeys.Private && } +
- {activeTab === CommonTabKeys.Private && ( -
- - Trial是一次独立的尝试,他会使用某组超参来运行 -
- )}
); } diff --git a/react-ui/src/pages/AutoML/Instance/index.less b/react-ui/src/pages/AutoML/Instance/index.less index 0c62da46..889faeb5 100644 --- a/react-ui/src/pages/AutoML/Instance/index.less +++ b/react-ui/src/pages/AutoML/Instance/index.less @@ -2,16 +2,41 @@ height: 100%; &__tabs { - height: 50px; - padding-left: 25px; - background-image: url(@/assets/img/page-title-bg.png); - background-repeat: no-repeat; - background-position: top center; - background-size: 100% 100%; + height: 100%; + :global { + .ant-tabs-nav-list { + width: 100%; + height: 50px; + padding-left: 15px; + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + } + + .ant-tabs-content-holder { + height: calc(100% - 50px); + .ant-tabs-content { + height: 100%; + .ant-tabs-tabpane { + height: 100%; + } + } + } + } + } + + &__basic { + height: calc(100% - 10px); + margin-top: 10px; } - &__content { - height: calc(100% - 60px); + &__log { + height: calc(100% - 10px); margin-top: 10px; + padding: 20px calc(@content-padding - 8px); + overflow-y: visible; + background-color: white; + border-radius: 10px; } } diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index a8707c34..376961bb 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -2,12 +2,13 @@ import KFIcon from '@/components/KFIcon'; import { AutoMLTaskType, ExperimentStatus } from '@/enums'; import LogList from '@/pages/Experiment/components/LogList'; import { getExperimentInsReq } from '@/services/autoML'; +import { NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; import { useParams } from '@umijs/max'; import { Tabs } from 'antd'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import AutoMLBasic from '../components/AutoMLBasic'; import ExperimentHistory from '../components/ExperimentHistory'; import ExperimentResult from '../components/ExperimentResult'; @@ -28,11 +29,15 @@ function AutoMLInstance() { const params = useParams(); // const autoMLId = safeInvoke(Number)(params.autoMLId); const instanceId = safeInvoke(Number)(params.id); + const evtSourceRef = useRef(null); useEffect(() => { if (instanceId) { getExperimentInsInfo(); } + return () => { + closeSSE(); + }; }, []); // 获取实验实例详情 @@ -40,7 +45,7 @@ function AutoMLInstance() { const [res] = await to(getExperimentInsReq(instanceId)); if (res && res.data) { const info = res.data as AutoMLInstanceData; - const { param, node_status } = info; + const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; // 解析配置参数 const paramJson = parseJsonText(param); if (paramJson) { @@ -52,65 +57,142 @@ function AutoMLInstance() { Object.keys(nodeStatusJson).forEach((key) => { if (key.startsWith('auto-ml')) { const value = nodeStatusJson[key]; - value.nodeId = key; info.nodeStatus = value; } }); } setInstanceInfo(info); + // 运行中或者等待中,开启 SSE + if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { + setupSSE(argo_ins_name, argo_ins_ns); + } } }; - const tabItems = [ + const setupSSE = (name: string, namespace: string) => { + let { origin } = location; + if (process.env.NODE_ENV === 'development') { + origin = 'http://172.20.32.181:31213'; + } + const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); + const evtSource = new EventSource( + `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, + { withCredentials: false }, + ); + evtSource.onmessage = (event) => { + const data = event?.data; + if (!data) { + return; + } + const dataJson = parseJsonText(data); + if (dataJson) { + const nodes = dataJson?.result?.object?.status?.nodes; + if (nodes) { + const statusData = Object.values(nodes).find((node: any) => + node.displayName.startsWith('auto-ml'), + ) as NodeStatus; + if (statusData) { + setInstanceInfo((prev) => ({ + ...(prev as AutoMLInstanceData), + nodeStatus: statusData, + })); + + // 实验结束,关闭 SSE + if ( + statusData.phase !== ExperimentStatus.Pending && + statusData.phase !== ExperimentStatus.Running + ) { + closeSSE(); + getExperimentInsInfo(); + } + } + } + } + }; + evtSource.onerror = (error) => { + console.error('SSE error: ', error); + }; + + evtSourceRef.current = evtSource; + }; + + const closeSSE = () => { + if (evtSourceRef.current) { + evtSourceRef.current.close(); + evtSourceRef.current = null; + } + }; + + const basicTabItems = [ { key: TabKeys.Params, - label: '参数信息', + label: '基本信息', icon: , + children: ( + + ), }, { key: TabKeys.Log, label: '日志', - icon: , + icon: , + children: ( +
+ {instanceInfo && instanceInfo.nodeStatus && ( + + )} +
+ ), }, + ]; + + const resultTabItems = [ { key: TabKeys.Result, label: '实验结果', - icon: , + icon: , + children: ( + + ), }, { key: TabKeys.History, - label: '运行历史', + label: 'Trial列表', icon: , + children: ( + + ), }, ]; + const tabItems = + instanceInfo?.status === ExperimentStatus.Succeeded + ? [...basicTabItems, ...resultTabItems] + : basicTabItems; + return (
-
- -
-
- {activeTab === TabKeys.Params && } - {activeTab === TabKeys.Log && instanceInfo && instanceInfo.nodeStatus && ( - - )} - {activeTab === TabKeys.Result && ( - - )} - {activeTab === TabKeys.History && ( - - )} -
+
); } diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx index 02a5e69b..b6f214ae 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -1,6 +1,11 @@ import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums'; import { AutoMLData } from '@/pages/AutoML/types'; +import { experimentStatusInfo } from '@/pages/Experiment/status'; +import { type NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; +import { elapsedTime } from '@/utils/date'; +import { Flex } from 'antd'; +import classNames from 'classnames'; import { useMemo } from 'react'; import ConfigInfo, { formatBoolean, @@ -38,10 +43,12 @@ const formatMetricsWeight = (value: string) => { type AutoMLBasicProps = { info?: AutoMLData; - hasBasicInfo?: boolean; + className?: string; + isInstance?: boolean; + runStatus?: NodeStatus; }; -function AutoMLBasic({ info, hasBasicInfo = true }: AutoMLBasicProps) { +function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLBasicProps) { const basicDatas: BasicInfoData[] = useMemo(() => { if (!info) { return []; @@ -142,7 +149,7 @@ function AutoMLBasic({ info, hasBasicInfo = true }: AutoMLBasicProps) { ellipsis: true, }, { - label: '时间限制(秒)', + label: '单次时间限制(秒)', value: info.per_run_time_limit, ellipsis: true, }, @@ -227,9 +234,59 @@ function AutoMLBasic({ info, hasBasicInfo = true }: AutoMLBasicProps) { ]; }, [info]); + const instanceDatas = useMemo(() => { + if (!runStatus) { + return []; + } + + return [ + { + label: '启动时间', + value: formatDate(runStatus.startedAt), + ellipsis: true, + }, + { + label: '执行时长', + value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), + ellipsis: true, + }, + { + label: '状态', + value: ( + + +
+ {experimentStatusInfo[runStatus?.phase]?.label} +
+
+ ), + ellipsis: true, + }, + ]; + }, [runStatus]); + return ( -
- {hasBasicInfo && ( +
+ {isInstance && runStatus ? ( + + ) : ( @@ -325,7 +325,7 @@ function ExecuteConfig() {
@@ -339,13 +339,56 @@ function ExecuteConfig() { + + + + + + + + + + + + - - - - - - - - - - - - {/* +
{result}
- {images.map((item, index) => ( - - ))} + +
+ {images.map((item, index) => ( + + ))} +
); } diff --git a/react-ui/src/pages/AutoML/types.ts b/react-ui/src/pages/AutoML/types.ts index f068d168..339a9e51 100644 --- a/react-ui/src/pages/AutoML/types.ts +++ b/react-ui/src/pages/AutoML/types.ts @@ -1,4 +1,5 @@ import { type ParameterInputObject } from '@/components/ResourceSelect'; +import { type NodeStatus } from '@/types'; // 操作类型 export enum OperationType { @@ -80,5 +81,5 @@ export type AutoMLInstanceData = { create_time: string; update_time: string; finish_time: string; - nodeStatus?: { [key: string]: string }; + nodeStatus?: NodeStatus; }; diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less index 1d5bdc34..83a91180 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.less @@ -39,4 +39,10 @@ margin-right: 6px; border-radius: 50%; } + + &__log { + height: 100%; + padding: 8px; + background: white; + } } diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx index 48cd0064..26da1c07 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx @@ -48,14 +48,16 @@ const ExperimentDrawer = ({ key: '1', label: '日志详情', children: ( - +
+ +
), icon: , }, diff --git a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx index 27f3354c..75d914f5 100644 --- a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx @@ -90,7 +90,7 @@ function LogGroup({ start_time: startTime, }; const res = await getExperimentPodsLog(params); - const { log_detail } = res.data; + const { log_detail } = res.data || {}; if (log_detail) { setLogList((oldList) => oldList.concat(log_detail)); diff --git a/react-ui/src/pages/Experiment/components/LogList/index.less b/react-ui/src/pages/Experiment/components/LogList/index.less index 3909c8de..18fcb21f 100644 --- a/react-ui/src/pages/Experiment/components/LogList/index.less +++ b/react-ui/src/pages/Experiment/components/LogList/index.less @@ -1,7 +1,7 @@ .log-list { height: 100%; - padding: 8px; overflow-y: auto; + background: #19253b; &__empty { padding: 15px; @@ -12,4 +12,8 @@ word-break: break-all; background: #19253b; } + + &::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.5); + } } diff --git a/react-ui/src/types.ts b/react-ui/src/types.ts index a7df0561..45884c02 100644 --- a/react-ui/src/types.ts +++ b/react-ui/src/types.ts @@ -114,3 +114,13 @@ export type ComputingResource = { standard: string; create_by: string; }; + +// 实验运行节点状态 +export type NodeStatus = { + id: string; // workflow Id + displayName: string; + name: string; + phase: ExperimentStatus; + startedAt: string; + finishedAt: string; +}; From 65c6baa7d6b1b679d5e5f9be1cc573b3e70235a0 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 2 Dec 2024 16:04:29 +0800 Subject: [PATCH 22/24] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateForm/ExecuteConfigDLC.tsx | 261 ------------------ .../components/CreateForm/ExecuteConfigMC.tsx | 82 ------ .../components/CreateForm/SearchConfig.tsx | 119 -------- .../components/CreateForm/UploadConfig.tsx | 80 ------ 4 files changed, 542 deletions(-) delete mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx delete mode 100644 react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx delete mode 100644 react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx delete mode 100644 react-ui/src/pages/AutoML/components/CreateForm/UploadConfig.tsx diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx deleted file mode 100644 index 6b2c63e6..00000000 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigDLC.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import CodeSelect from '@/components/CodeSelect'; -import ResourceSelect, { - requiredValidator, - ResourceSelectorType, -} from '@/components/ResourceSelect'; -import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; -import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; -import styles from './index.less'; - -type ExecuteConfigDLCProps = { - disabled?: boolean; -}; - -function ExecuteConfigDLC({ disabled = false }: ExecuteConfigDLCProps) { - return ( - <> - -
- - - - - - - - - - - - - - - - - - - - - - - - 官方镜像 - 自定义镜像 - 镜像地址 - - - - - - - - {({ getFieldValue }) => { - const imageType = getFieldValue('image_type'); - if (imageType === 1 || imageType === 2) { - return ( - - - - ); - } - }} - - - - - - - - - - - - - - - {(fields, { add, remove }) => ( - <> - {fields.map(({ key, name, ...restField }, index) => ( - - - - - : - - - -
- - {index === fields.length - 1 && ( - - )} -
-
- ))} - {fields.length === 0 && ( - - - - )} - - )} -
-
- - - - - - - - - - - ); -} - -export default ExecuteConfigDLC; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx b/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx deleted file mode 100644 index 8f8b7078..00000000 --- a/react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfigMC.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import KFIcon from '@/components/KFIcon'; -import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; -import { Button, Col, Flex, Form, Input, Row } from 'antd'; -import styles from './index.less'; - -type ExecuteConfigMCProps = { - disabled?: boolean; -}; - -function ExecuteConfigMC({ disabled = false }: ExecuteConfigMCProps) { - return ( - <> - - {(fields, { add, remove }) => ( - <> - - - - - -
- -
Key
-
命令
-
操作
-
- - {fields.map(({ key, name, ...restField }, index) => ( - - cmd{index + 1} - - - -
- - {index === fields.length - 1 && ( - - )} -
-
- ))} - {fields.length === 0 && ( -
- -
- )} -
- - )} - - - ); -} - -export default ExecuteConfigMC; diff --git a/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx deleted file mode 100644 index 255a6657..00000000 --- a/react-ui/src/pages/AutoML/components/CreateForm/SearchConfig.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import SubAreaTitle from '@/components/SubAreaTitle'; -import { Col, Form, InputNumber, Row, Select, Switch } from 'antd'; -function SearchConfig() { - return ( - <> - - - - - - - - - - - - - - - - - - -
只允许上传 .csv 格式文件
-
-
- - ); -} - -export default UploadConfig; From ad68c6d558f22da5d292309bdba8e426e016b0c7 Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 2 Dec 2024 16:13:12 +0800 Subject: [PATCH 23/24] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E6=96=87=E4=BB=B6=E5=92=8C=E6=B3=A8=E9=87=8A=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoML/components/AutoMLTable/index.less | 15 - .../AutoML/components/AutoMLTable/index.tsx | 305 ------------------ .../AutoML/components/CopyingText/index.tsx | 3 - .../components/CreateForm/BasicConfig.tsx | 23 -- .../components/CreateForm/ExecuteConfig.tsx | 101 ------ .../components/ExecuteScheduleCell/index.less | 30 -- .../components/ExecuteScheduleCell/index.tsx | 26 -- .../components/RunStatusCell/index.less | 19 -- .../AutoML/components/RunStatusCell/index.tsx | 44 --- .../AutoML/components/StatusChart/index.less | 8 - .../AutoML/components/StatusChart/index.tsx | 255 --------------- 11 files changed, 829 deletions(-) delete mode 100644 react-ui/src/pages/AutoML/components/AutoMLTable/index.less delete mode 100644 react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx delete mode 100644 react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less delete mode 100644 react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx delete mode 100644 react-ui/src/pages/AutoML/components/RunStatusCell/index.less delete mode 100644 react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx delete mode 100644 react-ui/src/pages/AutoML/components/StatusChart/index.less delete mode 100644 react-ui/src/pages/AutoML/components/StatusChart/index.tsx diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.less b/react-ui/src/pages/AutoML/components/AutoMLTable/index.less deleted file mode 100644 index 2d7d326f..00000000 --- a/react-ui/src/pages/AutoML/components/AutoMLTable/index.less +++ /dev/null @@ -1,15 +0,0 @@ -.auto-ml-table { - height: 100%; - padding: 20px @content-padding 0; - background-color: white; - border-radius: 10px; - &__filter { - display: flex; - align-items: center; - } - - &__table { - height: calc(100% - 32px - 28px); - margin-top: 28px; - } -} diff --git a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx deleted file mode 100644 index 19979d0e..00000000 --- a/react-ui/src/pages/AutoML/components/AutoMLTable/index.tsx +++ /dev/null @@ -1,305 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-04-16 13:58:08 - * @Description: 自主机器学习 - */ -import KFIcon from '@/components/KFIcon'; -import { useCacheState } from '@/hooks/pageCacheState'; -import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment'; -import themes from '@/styles/theme.less'; -import { to } from '@/utils/promise'; -import SessionStorage from '@/utils/sessionStorage'; -import tableCellRender, { TableCellValueType } from '@/utils/table'; -import { modalConfirm } from '@/utils/ui'; -import { useNavigate } from '@umijs/max'; -import { - App, - Button, - ConfigProvider, - Input, - Table, - type TablePaginationConfig, - type TableProps, -} from 'antd'; -import { type SearchProps } from 'antd/es/input'; -import classNames from 'classnames'; -import { useEffect, useState } from 'react'; -// import ExecuteScheduleCell from '../components/ExecuteScheduleCell'; -import { OperationType, ServiceData } from '@/pages/AutoML/types'; -import RunStatusCell from '../RunStatusCell'; -import styles from './index.less'; - -function AutoMLTable() { - const navigate = useNavigate(); - const { message } = App.useApp(); - const [cacheState, setCacheState] = useCacheState(); - const [searchText, setSearchText] = useState(cacheState?.searchText); - const [inputText, setInputText] = useState(cacheState?.searchText); - const [tableData, setTableData] = useState([]); - const [total, setTotal] = useState(0); - const [pagination, setPagination] = useState( - cacheState?.pagination ?? { - current: 1, - pageSize: 10, - }, - ); - - useEffect(() => { - getServiceList(); - }, [pagination, searchText]); - - // 获取模型部署服务列表 - const getServiceList = async () => { - const params: Record = { - page: pagination.current! - 1, - size: pagination.pageSize, - service_name: searchText, - }; - const [res] = await to(getServiceListReq(params)); - if (res && res.data) { - const { content = [], totalElements = 0 } = res.data; - setTableData(content); - setTotal(totalElements); - } - }; - - // 删除模型部署 - const deleteService = async (record: ServiceData) => { - const [res] = await to(deleteServiceReq(record.id)); - if (res) { - message.success('删除成功'); - // 如果是一页的唯一数据,删除时,请求第一页的数据 - // 否则直接刷新这一页的数据 - // 避免回到第一页 - if (tableData.length > 1) { - setPagination((prev) => ({ - ...prev, - current: 1, - })); - } else { - getServiceList(); - } - } - }; - - // 搜索 - const onSearch: SearchProps['onSearch'] = (value) => { - setSearchText(value); - }; - - // 处理删除 - const handleServiceDelete = (record: ServiceData) => { - modalConfirm({ - title: '删除后,该服务将不可恢复', - content: '是否确认删除?', - onOk: () => { - deleteService(record); - }, - }); - }; - - // 创建、更新服务 - const createService = (type: OperationType, record?: ServiceData) => { - SessionStorage.setItem( - SessionStorage.serviceInfoKey, - { - ...record, - operationType: type, - }, - true, - ); - - setCacheState({ - pagination, - searchText, - }); - - navigate(`/modelDeployment/createService`); - }; - - // 查看详情 - const toDetail = (record: ServiceData) => { - setCacheState({ - pagination, - searchText, - }); - - navigate(`/modelDeployment/serviceInfo/${record.id}`); - }; - - // 分页切换 - const handleTableChange: TableProps['onChange'] = ( - pagination, - _filters, - _sorter, - { action }, - ) => { - if (action === 'paginate') { - setPagination(pagination); - } - }; - - const columns: TableProps['columns'] = [ - { - title: '序号', - dataIndex: 'index', - key: 'index', - width: '20%', - render: tableCellRender(false, TableCellValueType.Index, { - page: pagination.current! - 1, - pageSize: pagination.pageSize!, - }), - }, - { - title: 'Trial ID', - dataIndex: 'service_name', - key: 'service_name', - width: '20%', - render: tableCellRender(false, TableCellValueType.Link, { - onClick: toDetail, - }), - }, - { - title: '状态', - dataIndex: 'run_status', - key: 'run_status', - width: '20%', - render: RunStatusCell, - }, - { - title: '最终指标', - dataIndex: 'service_type_name', - key: 'service_type_name', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: '当前指标', - dataIndex: 'description', - key: 'description', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: 'lr', - dataIndex: 'description', - key: 'description', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: 'batch_size', - dataIndex: 'description', - key: 'description', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: '修改时间', - dataIndex: 'update_time', - key: 'update_time', - width: '20%', - render: tableCellRender(true, TableCellValueType.Date), - ellipsis: { showTitle: false }, - }, - { - title: '执行时长', - dataIndex: 'description', - key: 'description', - width: '20%', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - { - title: '操作', - dataIndex: 'operation', - width: 250, - key: 'operation', - render: (_: any, record: ServiceData) => ( -
- - - - - - -
- ), - }, - ]; - - return ( -
-
- setInputText(e.target.value)} - style={{ width: 300 }} - value={inputText} - allowClear - /> - -
-
-
`共${total}条`, - }} - onChange={handleTableChange} - rowKey="id" - /> - - - ); -} - -export default AutoMLTable; diff --git a/react-ui/src/pages/AutoML/components/CopyingText/index.tsx b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx index 1df718dd..b4c56f4e 100644 --- a/react-ui/src/pages/AutoML/components/CopyingText/index.tsx +++ b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx @@ -1,12 +1,9 @@ import KFIcon from '@/components/KFIcon'; import { Typography } from 'antd'; - import styles from './index.less'; export type CopyingTextProps = { text: string; - onCopySuccess?: () => void; - onCopyFailed?: () => void; }; function CopyingText({ text }: CopyingTextProps) { diff --git a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx index dbf32e32..aec61d3f 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx @@ -46,29 +46,6 @@ function BasicConfig() { - {/* - - - - - - */} - - {/* - {(fields, { add, remove }) => ( - <> - - - - - -
- -
参数名称
-
约束类型
-
搜索空间
-
操作
-
- - {fields.map(({ key, name, ...restField }, index) => ( - - - - - - - - - - -
- - {index === fields.length - 1 && ( - - )} -
-
- ))} - {fields.length === 0 && ( -
- -
- )} -
- - )} - */} ); } diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less deleted file mode 100644 index 707a9b0d..00000000 --- a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.less +++ /dev/null @@ -1,30 +0,0 @@ -.execute-schedule-cell { - display: flex; - flex-direction: row; - align-items: center; - - &__progress { - width: 112px; - height: 16px; - padding: 4px 5px; - background: rgba(96, 107, 122, 0.15); - border-radius: 8px; - - &__bar { - height: 7px; - background: linear-gradient(270deg, #1664ff 0%, rgba(22, 100, 255, 0.1) 100%); - border-radius: 9px; - } - } - - &__text { - margin-left: 6px; - color: @text-color-tertiary; - font-size: 12px; - letter-spacing: 2px; - - strong { - color: @text-color; - } - } -} diff --git a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx b/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx deleted file mode 100644 index c2af2074..00000000 --- a/react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-10-29 18:35:41 - * @Description: 实验实例执行进度 - */ - -import styles from './index.less'; - -function ExecuteScheduleCell(progress?: number) { - const width = (progress || 0) * 100 + '%'; - return ( -
-
-
-
- {/* - 1/2 - */} -
- ); -} - -export default ExecuteScheduleCell; diff --git a/react-ui/src/pages/AutoML/components/RunStatusCell/index.less b/react-ui/src/pages/AutoML/components/RunStatusCell/index.less deleted file mode 100644 index b6aba701..00000000 --- a/react-ui/src/pages/AutoML/components/RunStatusCell/index.less +++ /dev/null @@ -1,19 +0,0 @@ -.run-status-cell { - color: @text-color; - - &--running { - color: @primary-color; - } - - &--stopped { - color: @abort-color; - } - - &--error { - color: @error-color; - } - - &--pending { - color: @warning-color; - } -} diff --git a/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx b/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx deleted file mode 100644 index a20a2fb6..00000000 --- a/react-ui/src/pages/AutoML/components/RunStatusCell/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-04-18 18:35:41 - * @Description: 实验运行状态 - */ -import { ServiceRunStatus } from '@/enums'; -import styles from './index.less'; - -export type ServiceRunStatusInfo = { - text: string; - classname: string; -}; - -export const statusInfo: Record = { - [ServiceRunStatus.Init]: { - text: '启动中', - classname: styles['run-status-cell'], - }, - [ServiceRunStatus.Running]: { - classname: styles['run-status-cell--running'], - text: '运行中', - }, - [ServiceRunStatus.Stopped]: { - classname: styles['run-status-cell--stopped'], - text: '已停止', - }, - [ServiceRunStatus.Failed]: { - classname: styles['run-status-cell--error'], - text: '失败', - }, - [ServiceRunStatus.Pending]: { - classname: styles['run-status-cell--pending'], - text: '挂起中', - }, -}; - -function RunStatusCell(status?: ServiceRunStatus | null) { - if (status === null || status === undefined || !statusInfo[status]) { - return --; - } - return {statusInfo[status].text}; -} - -export default RunStatusCell; diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.less b/react-ui/src/pages/AutoML/components/StatusChart/index.less deleted file mode 100644 index be1c816e..00000000 --- a/react-ui/src/pages/AutoML/components/StatusChart/index.less +++ /dev/null @@ -1,8 +0,0 @@ -.status-chart { - flex: none; - width: 380px; - min-width: 380px; - background: #ffffff; - border: 1px solid @border-color-base; - border-radius: 4px; -} diff --git a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx b/react-ui/src/pages/AutoML/components/StatusChart/index.tsx deleted file mode 100644 index 2c8b8aa4..00000000 --- a/react-ui/src/pages/AutoML/components/StatusChart/index.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import themes from '@/styles/theme.less'; -import * as echarts from 'echarts'; -import React, { useEffect, useRef } from 'react'; -import ConfigTitle from '../ConfigTitle'; -import styles from './index.less'; - -const colors = ['#c73131', '#6ac21d', '#1664ff', '#f0864d', '#8a8a8a']; - -const color1 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#c73131' }, - { offset: 1, color: '#ff7e96' }, - ], - false, -); - -const color2 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#6ac21d' }, - { offset: 1, color: '#96e850' }, - ], - false, -); -const color3 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#8c8c8c' }, - { offset: 1, color: '#c8c6c6' }, - ], - false, -); -const color4 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#ecb934' }, - { offset: 1, color: '#f0864d' }, - ], - false, -); - -const color5 = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: '#7ea9fe' }, - { offset: 1, color: '#1664ff' }, - ], - false, -); - -const circleBgColor = new echarts.graphic.LinearGradient( - 0, - 0, - 0, - 1, - [ - { offset: 0, color: 'rgba(255, 255, 255, 0.62)' }, - { offset: 1, color: '#ebf2ff ' }, - ], - false, -); - -export type ExperimentStatistics = { - Failed: number; - Pending: number; - Running: number; - Succeeded: number; - Terminated: number; -}; - -type ExperimentChartProps = { - style?: React.CSSProperties; - chartData: ExperimentStatistics; -}; - -function StatusChart({ chartData, style }: ExperimentChartProps) { - const chartRef = useRef(null); - const total = - chartData.Failed + - chartData.Pending + - chartData.Running + - chartData.Succeeded + - chartData.Terminated; - const options: echarts.EChartsOption = { - title: { - show: true, - left: '29%', - top: 'center', - textAlign: 'center', - text: [`{a|${total}}`, '{b|Trials}'].join('\n'), - textStyle: { - rich: { - a: { - color: themes['textColor'], - fontSize: 20, - fontWeight: 700, - lineHeight: 28, - }, - b: { - color: themes['textColorSecondary'], - fontSize: 10, - fontWeight: 'normal', - }, - }, - }, - }, - tooltip: { - trigger: 'item', - }, - legend: { - top: 'center', - right: '5%', - orient: 'vertical', - icon: 'circle', - itemWidth: 6, - itemGap: 20, - height: 100, - }, - color: colors, //[color1, color2, color3, color4, color5], - series: [ - { - type: 'pie', - radius: '80%', - center: ['30%', '50%'], - avoidLabelOverlap: false, - label: { - show: false, - }, - tooltip: { - show: false, - }, - emphasis: { - label: { - show: false, - }, - disabled: true, - }, - animation: false, - labelLine: { - show: false, - }, - data: [ - { - value: 100, - itemStyle: { - color: circleBgColor, - borderColor: 'rgba(22, 100, 255, 0.08)', - borderWidth: 1, - }, - }, - ], - }, - { - type: 'pie', - radius: ['50%', '70%'], - center: ['30%', '50%'], - avoidLabelOverlap: false, - padAngle: 3, - itemStyle: { - borderRadius: 0, - }, - minAngle: 5, - label: { - show: false, - }, - emphasis: { - label: { - show: false, - }, - }, - labelLine: { - show: false, - }, - data: [ - { value: chartData.Failed > 0 ? chartData.Failed : undefined, name: '失败' }, - { value: chartData.Succeeded > 0 ? chartData.Succeeded : undefined, name: '成功' }, - { value: chartData.Terminated > 0 ? chartData.Terminated : undefined, name: '中止' }, - { value: chartData.Pending > 0 ? chartData.Pending : undefined, name: '等待' }, - { value: chartData.Running > 0 ? chartData.Running : undefined, name: '运行中' }, - ], - }, - { - type: 'pie', - radius: '40%', - center: ['30%', '50%'], - avoidLabelOverlap: false, - label: { - show: false, - }, - tooltip: { - show: false, - }, - emphasis: { - label: { - show: false, - }, - disabled: true, - }, - animation: false, - labelLine: { - show: false, - }, - data: [ - { - value: 100, - itemStyle: { - color: circleBgColor, - borderColor: 'rgba(22, 100, 255, 0.08)', - borderWidth: 1, - }, - }, - ], - }, - ], - }; - - useEffect(() => { - // 创建一个echarts实例,返回echarts实例 - const chart = echarts.init(chartRef.current); - - // 设置图表实例的配置项和数据 - chart.setOption(options); - - // 组件卸载 - return () => { - // myChart.dispose() 销毁实例 - chart.dispose(); - }; - }, []); - - return ( -
- -
-
- ); -} - -export default StatusChart; From f4969b672e0fae91622ba6d836ba42ab5a8927cb Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Mon, 2 Dec 2024 16:15:22 +0800 Subject: [PATCH 24/24] =?UTF-8?q?chore:=20=E4=BF=AE=E6=94=B9=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/iconfont/iconfont.js | 2 +- react-ui/src/pages/AutoML/Instance/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index 65e19f39..7097beaf 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,z,v,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(z=function(){document.removeEventListener("DOMContentLoaded",z,!1),l()},document.addEventListener("DOMContentLoaded",z,!1)):document.attachEvent&&(v=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,v())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}})(window); \ No newline at end of file diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index 376961bb..7b416de1 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -140,7 +140,7 @@ function AutoMLInstance() { { key: TabKeys.Log, label: '日志', - icon: , + icon: , children: (
{instanceInfo && instanceInfo.nodeStatus && ( @@ -169,7 +169,7 @@ function AutoMLInstance() { }, { key: TabKeys.History, - label: 'Trial列表', + label: 'Trial 列表', icon: , children: (