From 4be2e2cd22198515ae77965782ba0a669070c3c3 Mon Sep 17 00:00:00 2001 From: Lei Xu Date: Mon, 14 Apr 2025 14:40:41 +0000 Subject: [PATCH 1/4] added v1/hub/server_categories endpoint --- client/public/images/mcp_server_categories.png | Bin 0 -> 34615 bytes server/src/lib/mcpCategories.ts | 16 ++++++++++++++++ server/src/routes/hub.ts | 13 +++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 client/public/images/mcp_server_categories.png create mode 100644 server/src/lib/mcpCategories.ts diff --git a/client/public/images/mcp_server_categories.png b/client/public/images/mcp_server_categories.png new file mode 100644 index 0000000000000000000000000000000000000000..a40af9da8e615295f316cf75af73b24d485ac984 GIT binary patch literal 34615 zcmc$mWl$VZyRC6|m%%N;-QC>@?(Q1g-GjTk1Pks2C%8*+hu{t&aGI~~xmEZ7J#~Ie zP0#f1nQ7U*-?i4WCrVjS3K@X_0SpWbSw>o16?onQ0|W1dg8`O~{C)-iPvEYqQlenB zQ=k*zg}AwvjD>;%7%i|22L>Kt4F>hm1b7et4=^yu0&p-$;2Hemy#k2;yLPVt^1qkC zyFVJffXW@gz=XkM#6{FS!O!$zjj%K@^X^b&;NT0X?>0+lrI}~!j_ZQe(UmI^=0temphN=`guRs>@*Vq$)16LQcP6@n#xHt$8u|Ec56`EL3Rn`! zhrow|vZugDvIQYHzZO#e<6yqk$2t}>H~)>Y;45z@@z|PZB36|AqYO{6HP6Ll$s8{((fi1 zfOJZm(%092gQQmzQwK4}Lt0y(KpmtcCW`p`_mJlt*HDUPmNTCQN7}0|VEZF@CVW$R zQ0r?`Q)i$~UMK!Lu(if+bTd1_7FgrO>qXu7Y=hU%_^MJyVr zEleoFf^Owy?XOs=h+J^Ro5Ekd;Ovxh)s|P+>;~@k#eo8Ij#*tz9d=Y*;EEYN+grnI zWm8o_^^Pd}X@vjU>h;FRVh>YilvN1X>gpb_Vb9_qsZulf!_Z^Z`>h;XlXA zTtb*n=V~0~Sh+Pco%`~*3&vx67*5-_NMj}Jg;O;_%Ak4_*c(4CYzzlvQ9A3_7}@1x zd3FAwGRs7^-sPh|yYfNt1R8E0q)M8WKQ3%rm+;zc#GDJRK%XkR_UU57NF+C@*m(Zf zxxoB$<_UY_#TQ9c!yMx)p*5ErPA;!w%8isMO=!|ORig_$g?UP7Ojcd;a#cG>F-O6p zjm`Xy;UPgaH@l+#@+eCVT~0kyeEw(FKuReZ@ms+~>c(*ZBO?}ms>q*s!-B{*@lz}U zamH84F5nXK*$Xtbfh#-N(o{PZR>x?Dzad}#?slr)O55(`rRwI3pf597LjQ(U+M`Cm zbK4?UL%K#ttnlYsn3{z}TDLyz*PdudvkZ$^>B%B&T#T`A(Ds_DI77T0vpwRlyuN1$ z`uknnI17=v#wiI&!vrjuO`m9Fcc)d;K#`>|B5zo~sH@3`g6ewTvUtCq$~W6A(%()n zi?fwA>{*JfV0SF0R>bA=u=WnNlXv$q~_IvMEOux;x zIX}*?HTUA&-`TbJdVBN!d)iY{Z!nS2X|cnPBlh!19Ddo2nYWtCVXBafBao=Ftm)Tn zu`6}#)t3%|MNOj4&B?L4+!R={*}QUR)QhBy#TW9OuXsoJ-C&wZoBFBF(8JHZWABuKqf-icl!!^2 z*fX+zyxJK^0vGIkdm(h&A4SI>f82Ukn!9Yfaz_2KFuwTNuH5&zH@2hCf+sJ~P1LB)rmxROhIa?)hBuSX1(hfD z^^NRbPucxxQOio@(xqSM>bhiOa2U!?=F4Zfw&)8+N5|Ps2Bm{xY}T6K|J{r;nFthI zz1;WCX0jPKSTZjV~lInc(#AbuQhVK7WWnrnu3DjfVxhtoRh)u zjjh>nD=*9us`abnTGY31dz@Tzz;UyFI8$mrGm>e&v%KED6tBKJnU8QkTaiIWM=vvB zVQFyN>4%A=wwJC-y!z_E$zKPNhq~9VozQ6-se( zAdR)ZJ)CR`cn5#kiMXtE+!AEBn9w?0c6-?ldCauDea=~)?ee-UzM+wBrf+eyjmY7A$gloE$_Qy#uLlwykt}lXXT7*yS1jM=jN!tuR3c!Z_l`% zzJE&@bl*)QA!(9xSu4M{Da+yzt5hkLRNYQi5-gP>&ZImF3r%Ct z{ju7X&U|>3=kw)PA67QU9|0FPw-WFB(^<>>$+8iWfLLC)HietNkC&DhFjsrWvjggd zqR?YL_luf?yM623Hj7o?HLBp0Sa9UjGp>zn?pH1IlMtCQf%|$i2|1nimFD=fw#|In z91^-DDyNj~ZPL)pDe;}-51L#QJ|%$=-%5BsyQiwza=W5ZYMVQ&ThiTceaBEf+CoY) z<>-@Dszi=bFZZfAUS|4Sd$Pakx3^`<@ur2|ySiRc;=nB>(i!y+KA-OGG3c{fDQ_(- z_P*!s(zuW}+y7olfkB=t5D{x{Uu$)g%H{LGD6H0PWi%#!xnDL^x!m-SezyPJ;_Pp^ zN+RIRu8?tshIk+{bxDN^G4V&Z_x-KYN-d+7w??~B(!t?h+n1WPX9V(J4Ei1F4lWa# zPX2gXXRG=m?!LV#f9?~n^R;F+c-%QrF);Y)meAOm%kpu98p{U7Sz_`H6o1rDCre7f`U^0I@ayHY(c8*!S}PgG6%+JGcz_Ou ztTi!kEM-HnZNMl60ypu=?Md65<_EMT>d={=#&`AUwC!pl&NWA@Y0W{c6_$l7)r>^M0{YPBg3Ftv8pc(9viOdoZaZ z?#(@&)StStWWLtSKE{QBUA9rS_+nUYGZk@F81o-s4sr4pvkO*RrJm7>@`ZjoDotCn zQR}M%I%9+!amAsbQ1Fcr%06ju)VPdig<=tKWfX9(Lt+sq^NrIMwhfH>yfO2II?XoJ zYB+~v6Xg7Hx~&f8HUy|aC-a6$TWU%QWvD{0kuUduMy8T}k8N$`=XvfrtF0DY2p>BY z_d|sW|8aZVc#M>RKi@t-S14D^U1~Io;VH?_H;iY`@Z#mLT~ZHi*KMu!#;_fNw7cQp zAi_1byjX8%RSI}M$W%pMifESz6u*6QZM8qY-gkJnAR#Qm-&j#U{pq;yo!d94B`2!r z^sVR4L!F=oIi_^-$mDbMmPU6$`h!iAH{>`EwHYn6WeRY zc`sGVbI`ub&!my5m%309tT(}g)o0o&@CFD5%*B$a|Cy!Ux z0VQDM@_N;6+@@k0)x-JkX|CHO(CiH}leil*90XiE0X?>`?dje12+15t!0S)f&)lD@ zR8&>TCv$kje(ca^@_C?izrXzj`LHEGch^{rOOg1A*BNZ%jN@HF98pgwk!cZf+MyZe zvRjkKk-UT1?t*d7_wF3zzVTGaXR$}6Q?*>piMAYeK5qKJ?coW$5Fe_c@!423WD2AU zj1A4ls+B9I9#GQnuv^c?raQYlY&Dp|dR2TqTZuZ_x^;;~hFv0_MkZR{4+V+DvP8@T zcxH(f1LErzS-nzgKz`3do-04TWS}d>nVKy57oYW=bhS;>j_40hC7z|jBjlGfJZq;bj4U(`qckIEx&eiFn+WO z%}z6HtvF-xyvV|&J0nQ^#p&=kP3+=kyS2wDk55Uc<Qi#%-DMQdtXi zBvrd`Rqa2gK7xL$NXU^_jP1XJ-q>B0Iy_2wG!}F1Pwdfq-(rYXd3MkDMT<{iloR;) z>~o$zn;58^)1j>3!y@5I=H};h4WdWqe{-hJ*i2ueMR~>LG0K2E>K#SmumBYH7&uid zSMuK~$02YwluyR&rr~QEptekqa=#~Ad)>zOpHyN^HZ3i4 zb>oI|aC33mSB73)fjTr_=-CbIb}m>5V~Hw`8(|Su<=wL}YvAs1!m{P!vb@<_DU9dD z`8&Mn0Q0qnD;=`bq^zs~CsskasBO?Jx(h?B7Bt-JQVLep-y4nn`iX}lSEXW?r=;MT zv@+5l&M6XZ%TW<-0jND)E9z%!Cja2LlNg_&U(~mSD#!4W$~*rSuBU;*gb7siXOWHh z-BYFMi-N}(JN6xZ+Mq4=)7y7&{Yvr?<@;;5)a{}8@AyPSte-1_nIuLiBl~dP)o<$H z&B`w}e3;Y36=1ARXpAwzql2Zb3rb2TQ7B(2r3N~)0>r3Bhs418n7q)LDX$^JDgA*l zJpHNCd;by=_Khs?8Y}H^x)ucQ`3X?a!H>*q-RRV$pywl zpHa_cs-n!!# zqsmIx{98H#J`jpdeN=j^C5Q=|hw81&IzklI9z7G6!Bxg;=4s+WJJk4`HDukh>7X7D z&B@QIGofw!18lY(9K`Fe&aF0~8rXPjC2@TXkJCAhn(eeZD1$`L%O%yFC*b2;zl&VP z-Zublh^7@vAsL&rJzO>c;SNR~a=afXquc$08&euo!{T=AG<(x0nZt8y9_3>T?}%>1 z-S;iD66xG?oMDw4cP8G;Sh+||t}S)wr}>~*DWD5jzr^CtKt#K^^r0EcAAybTq!%5G z=|(9iJ95fx=;vx9TCj?PU}qH+Ov&WY8MLNGVtmJalE0-#a6A8UE!aj()YLMn3#r}* zxy*h8`|bXxrgc-9g`GWx!gd;SJ99k~+87*y9abb_5?(7f5(fq1ZD=&H7*Du~3~`<| zNZRM3{b(`<*G7o8 zudM06wkJYDNU~VE3?C5+P z?0E~e{A0^2g@r8Ku{gy3KT`}>awEqzn?q=(t!2hO&k+|Zj-`bu4=^zZ=MyO915E5Vl)R#8!Sxa^y;l#+%Swl6nlAkS|ep-I`S-0tHuWjm#*u3ATJnrTYJaz zet%}?#HX_F3lt$Jemfkk)2s^`#=}Vpcgf}bn&>)@$g&+byz}pVfUw2DK#%~kU6xns zehDvD!&Dk9&e2Q;5c=)I1T*U2pbf-$a;X)4#eJT3qFxFY%te@qOe1EKIfe-m+4XG0 zv1ag&7uCZlzS@e2q_Pk1BsA$XpITjZbB(8bBa)wHja;Hi=mw6`!O6!g6rX3Tf=T=;a5O?HtaN>$%y1eZ2cv} zaVQlx>k0PWe%r=(XREqheP~(*IvQ2#G813A=2guu96 zZ43RNhlFCz7!K^4pUrYjZ(TMOV>d-(GTbi2&rXqKahB-{&k#<>2jk#nOspNInZsVn zQ9-H*X`;))CCu-J!B0<-zeeIC9$AU98qk6A9eTdSg=4Pt%GSCtnosIZzdjuMQD+LC zWtm}$2>}KB$Dh~u-vux@zmW0C5uhtqPkerC@FIR73k1`G4#qAxy}=Mq*=Tpg-P(pV zM}gsX==0k%P9|iBMeXPs1%;?GLDo|kDdXT;%R16L;^=@(_1aya8T6iNgquw5HD|w~ z;E~4~5Id;XNn#u5PL#m8@8`rtlIKCrow(wjn5{tbdY|$W@x>WJ5eX0}j!YXA%~m<*M7D>7@FuHrgV=7FzvIRX{YMlN{DcH!e!)A$149ZJOdIkjOxtE(YIN>l#tXUa(@S~7;VAI-Pj+NEs?|%jNds=^ zMu^qko*S33e!FbusZWio@mC1u5$}&3W~WE+X_z&cE>!l-=qynRe|f(Q*oun4e2VC! zIa{t1x<7HHVXB(`v7#ZO6^KcrSOUli)-)4KPE@+_t})Pfk9MO)m*f*!_w}%DusvM; z!pXdAaEQ)XTS64zT9`u}Sgx4*wB}47WpJINKB&QteEVMRiFfNY0ZX)vpJqKvuRqTT$u?G&5D{YerAp{(uKIIihoUs_>+ z1Li*YjRkc?tNu=PX_O$r2^crEO*?o=hQQ@O_2J9}7G*jF^|ES!`8}EqDT{Y|823vB z^hpCKljt?vtEaGFZw>=7hP%9qU9ezfQ-Kz8sT);nLqT0U1)(gCn-!p;Ak_n)g{pb$ zsk}3(Be3B_HW#gwbAdY0qCtJNhF9(X*GJ=FB>d{)gXbM`*18IE@b&Eqtq{gyM++v{ zDb^$*A&Gh*hk`JM;h8h@e2q@(2-L@7CF&SQ_I#Dt*cDYFKSpv zLz+iHhEL)HHkt(8QiV!LS63Iper9JUAK+Ojn3U*sd8(LH;b_TVD}z3XftbbdjJ+YO zs6Ks?J`4>*_C&PT0xV#3lO|)RgRp5Ual_IL>V8pj**0`HpazH%uoeGZHEH+J5i@A| zogp!?3SaM~f3KJjSCZ(iMF@+^iSp?rqt$Z@`AXV8d}UkfZ`$7bCR7Mmth%Lt0pD}B z#eVts%k6Z{-55=NR&A~ZuS(qhb|6_l9wsJ5+2{V>Vt8LCD;)39g+IBLlT~q^lD!r* zANK}NYbD)6=RaO-xQd-E?6&YYZu**#5~|%dn2yZV>hn3Y8I(cTln;LF51Diu&sB(1 z5DtS5@H}qJKiHrsaK0&}X9;}d8g;g2;N^A2BSToF&eS+N`MxPIXy ztQ?1;vDvL=(DDqI6C#hLz7eyf!s6@${;mbD#XbXppl^v0c?%$I{Q%`&*40%{q*4&@ z-bty$ONu@tByT9WY|^*-v+Tt)0VniN#+H@>awQQaEMQ<|$iqcf*U=PlW#qx#Au7O4 z!eZk5)v^pN*TB`$Gh0|z(4Lo;t5?JGm5lXRsOY5XV__z=oXQaq`geyZ1)gtwimw$` z*YiRv@N!JyYZ@RG2xqm`BS1l`Q3g2c%x4~a9hW`MLyQ536Zr6y-?;)=fbt;AE3um~ z<_%mdesrg=I`GkJtttI(*Vi|`dv+BEx=)&n|L4zCs9xu@6&9#KO?|D~es^(GU{^Vt z9X4diYa!>+kIG>KmiXw9YEAu37k{hhvY}ilIFjQK|4iM>E)ai;eRW#~%$~CAwZd)K ztk#&8YttY$`-X3<-;p`|S%mrgU)x$dE)C|}WPhk>`3?g{nhiFs`hv0n4W}cAP%i>2 zV+nY50=nnDT^zKC?M6q<30_7>U+^G~`ohg|{*iUrgBEwGfWLA7!sc(sUejNeW@h+o zSLh;r4p89~#{$FZX2(GwyrA#+&H4U1H?eDQcJs-{kwpJRjoAA3p5b)2PE)7XjV zmaw{VhxLbP<5R3wSFhVilBa}m+9@^7#24iYFY@?8WEB)@#?7VA6E2afY*f=5{kv=s z-_R@M4~=hrH(4{tF}yl4Hyd`kGt3Kaf>=kEXa11O_b5s*)Gd$OA~F-^2Btv!2Q9b5 zQ=itfwoL#0gH91Xzx^|$#0m`Z2n+Xj19w~ea~#Lx%K7~Y8k>gUT;C1Mp>c<>=*pAG zr$ah5bF<5Ou?qX==b-HEl?Kxr zaxf?qi%r`EF*o}a+7e75_* zcYSGX@`pa7mh2A2;EvmddTOR0=?z&+o+9Bf2LgeBNTX!{BdSOq=6tTl19k-3cN8u1 zXHz~-G+1nqNWIAfJb}mY3>lK6p!V7T2Q*A^ouik{d|8~AO0yx<#~sf5z3up_NR)2( zP|#&R5{~HVg3NQpq&A!fpOHBfm%6mwtGHDoiQ%vwdWWsQv(s!0gLs*&O1qJBOZ|Kw zxoEXfh)!l%q6(3T`q1?L57$1dDjNj9r*ozMKkwL(w!p)w+;OHOO_OOWLjs{Ue}hxJ zVb{Ij2BHXLLh9ub#awUS$=d16`|A{#+TzXhZad_#(1CSSB{R~;UH!%14xMrO=ReEL zQ758EJ)eGx)4_?&5a3b?_89p}PUy3wbsg$0;Ys=13)A(>FB2I&r!vSB)1w)YpN>m; z6HO4Y2#h+)2C_cu<4!!XY{7i*5J=`A-9f^X)T6ZG&nW2Yh!`Q@00Y7g!ho6ZfOS?BxT45n&cg7;9k5uicA>yJ`+d<0<4?eFuKpOf8yM#Vtc%lw z<3Jh$ALv~00zPMjXt_BgE@32<50@T7)b)5X$9LZDc(X^B_zk2BAc4Wu?)DHb!KJ2BKQ!+QI zF=$#!ui$oPCnYl}r-pVB8(;(|Q1FWIe+q>z2QGzhE`(shhJ`90#S>9>3H>4!T`d!8 zos~iPaxlcAuDmqMLRbv>9E}Bg28{7MPDbwil#`>|(mUU&&{yF4(}^BX*(6{$mp`Qr zxRyA3!`Kc_$ZxxvrQyH1e)5B;Ed)~Vey2X>Nw{xFP)soc50*cK$@^FEVJx0^|2(f! zSBw@@@Y&-*Ol>HHawZcAR1`NP=pqw>5 z(S)&r?4mSh0-P^>CsTC>mVY zZ;Vc7i_4)bIn8er5%`LUeVrhVG%ae@6r4~dZ*QwfaBzmTq998gZVwrtw&uDnlBndM zMZ=BSWUU<@D0Q> zk@c=QoTMlIXSohH!+A_Sz%J>dtTc8ZPiS!TB5~O{ULnmCbKyNq7Uho!VdmA>U5_Y+ z4G!uk70F;SA@;i3770@Ndzk(FJ&ZQ90EW|UWwh<#_c4GW$en4@&{(3QSbaew-HRs{ zkPk!!1Hyc|!Hn2oZoN`jY?*;|EFHXsALep6gCXB{4rl*fPXB5Z90rFA)im(RREe%0 zyIg6YKuX^xvj>lRj-tskisP1y?|reUz3aAJcHqH<^Nm0us&LrgP3rZ!wRo_xM69B$ z5aJvG-wRDt!ZyAU(4Kz=I0_ga*>Pw3#WUtBez8Hy#Lq#A?tAz#14BcZySsazSw1JK z$w}d%KMTb8uagR*lZpyZi z16GJ)hoCHSFZB0J4d;$zaQ6PJar#=XH41F+_bm)5i!?khB7XqXa8d09IPT#hCcY&B+l2iKoHV5Lf1pCkVM%QTeQn*0bdF%DzJJ_aH-?!Jlli@Y7ag{N8AXQv&+TrS+!OsTO%{_ndCS9@ zr@}^s&&5SWR%=bxTuA@Q^Zkns#x>zEa9e<)(IWYpH^sx?-k1Rj3Mv{Ni3}3PL{E?2 z_Sz2bHjzSVj?l47=6CBm18VG6z`Iz%miy%hq zJcS~@|0f+@Y5IyHD z;~z!yw7MK1%=1?nbO*B_(kNt!1A=GAJid&zt5U5zy0Oe=@ie%6r8nT6FkHa=7vkCX z=fJ=4MI*#MITlknWCj{o6LtPZMg`nn1+(>bb0sorryHHEfhk3dATEF}kOmSo6KSr1 zhq-ujr$sLKPm={eaFy`(w1--y*G`+=u!#4`p+AJ^D>hVkwRn5?;PUvd;@ zBG))X2EvJIR|jFA&JaX>5bH*Gu9pe_&%m5f`7kK_wKf`ZJCI{SeLNX$$nc{mQa$=G zAuQWZm7V)ofs#n)Fh*7b^y%w|k?}RH8U-7c-W4ce%zG--|7RQb-8*I@T@a9*lOM*x zO2x3fOXHA`Q;6yfmX2eMlaMUKS4zJ193T_N*%&zYJ#ZUEGL!p;4>s*E^xIInT$R&l zntYBE0-;nIXwOuloeGf&Ce~g>jHqauetH$96dNd2R)!9sif!@8uRu?Q095R7#)@aO zDFf~Z0qPnf6;+&=Ra>O8q<&m#LhLMyvl|t4-rV!vUT7#K5Q-is$LFQ2UU=U%t8xb{ zhV2GUIss`wXjzPcYGJ1>$d6N!e8tpDJzkceK8eeM;FwydrDR32$bz z$)ao!NMcGMgCgiV8jf({ZeZ@mM~H*6oh0c|j;;b#W;H)&f@^|xBn`Vt!kO6E5bUv7 zFmFn_-*Hcn<#H3F?537Ey?NyckxvAK@NyvcB3&ct0$TWSODG=F)N9r-z&@FqKbLpI z;H(GT7TcJN?h6ug*c+3(X;sn2$5+YAjTcmdc;D|9b`pW*@O+@BIN5c5QAsOOx+QyK{?v z=P{NoF1s~5V=Frjwz5{l})Nq)%KBG&q3Go(SU&l*De@8L>a<@iKi|b>l}{@h;#>3 z;V?Rp zX`z9r@OEPa9N)*YlVW8Sn~7nOv@Nk5X?^6#A_eVU?#~xf!|(n`=sVbpDG)9d$_62& z{Bjj5P$IkGE*c_}FUAz>CqptZGLneL%!<=9?^bJG$LO;W;;C;Npk`Cghf(H*9-3;tTKZ{ z(99n%4q$g6x^JgC?*P70n$)xp5+LltdtP1QNxv-C>T5#K|Dj3IeE05IBNzt{31yLU zsC8R%Zx*Xk>((LLCl8KLeqW^tCB&w#MubWT5H1u2X-p77(!GrLG?eFU@E&>q1V&nE z#`-_9!M6mm0Vo*X=bLuRE&P(_V)H$cTJ}sziWO>60 znQugV@$`k}<7sN$-d_*MQgF-2P4ad6Bk19Z(f6b%zY1^F=(X!DdH#Jkm&)d}$B-ZS z2%Uor}%@J%Tuk)DQd^oBB45Asb#KmE{`%nSQf=e}i18Hx>hT*2swO zugbm|0eX05RzKpuDS9_>ex*T{H*g=w+5YQ+BBAXqRv6!=4cw#S zhIw2<0@|!RZ~8w^J?PoX-uEdTGglO|N&tS@|6AaBGe(2g<3mn?=x9+GQqbM6V+B7= zwfrl`d8NM4gc=w(&1MsrlYQ<_sl-0hmYIm)#aT7EpW79H&i+0r*_SpNVza0McJq|MK_O98O&MWJBGp=gEryIO%_59#Lvxc$l%AGr1D zWv_=cw(t+|&-kKnHE{KyjQWCIBih4=*+|Zq8)Z7h_hYfdufVO?V!|jcJuOZ4;7Kw( z3SlG=ZkwY9PT^z_;&}fT@uDq)@)7}&*VL%WzsVi2@2*)xwg(Tt9dY|IY4SX~l##(c zdon}*gfQ2l$*9um4dnpq*Rh$-6M*XWT&!B8UuQa&FxN#ZE80Xsz=bJhk=hI6{#5ZU z%3c_al?)kfH7pM^6S#DH#39}$d+kq~P#!pYJef7!+Vzc;_4!(d!a;Gr5DCP)Sp5aZ zQjukPq-qf$cVdjH{BI{)i%!0g2>nd3t$}mpkb~mB0jj5P;K-2m!>$kG8f1{8f`onk zu-mOL=c!GZIV}cMx*kn=v+_mWjHuhqkkSa60q3YJ9I;(Z`8~`|5}S@17HKxt(|dp= z1o|rrkM>_1%Xa%b?k+>s0f2Gr zG&lvU{17ZjPG3g!?LPQoj~^fB*RAF1YdtrNZ{{7_Vn8$=9ysgy_UiT1s>}EJ2mkg2 z3xNXm|MQ!{dsgq38DIRy4c!p;#<;#4>OwJPZE1cr%U(#v?YUlx6S9?3>hcL8%gV8YXEJx}e?LP}w}nF-uK z)}(rP4kBi)3~HJGBFr~1>;2LmCg|e|huiad1KGSMCZ9=6k_&N)?F_>2Hj8z=g;ODC zl+L6)NNYXi^La4ZisRx8n?H;OvwBS}p-GBOEc8WV!wLU>5^PXahQ4_SJh9#=jebAH zMn**~=euYVGmw9;%L^fBWdWRbHUc*F#j4#vKxm7E4-KemFjsxO%y@=Ptk| z=LUovtb88`3}5H=shfTJhx_xmR*|@h4-`^stI(R|ww)iFbS_KAO-y~*OjC?)y!yX^ z+(+_vH>Zu6`!Kjzk7d)S7>ejnkV;fYDaMF(%1j(V!1HAv2|nKy>*ys=Tf8BT2Qb`F6H7LG`}iF3kk=45*tZIhAK7U*f|y0c*yl6 zy=lF;+ml_{5iQK*TX=LOwNX~M!KNvh1MQsQSS(dYz<ag=BNUf2x5m zF0@qtT4MNj=cHYoi|$IyZVvG(87n~&{JTFgs^S}>Qn0pz^(@* zqg&9$?(=+x;cRe}+tA1ag}l$3m&fMkb%jkBQvaS-*HH%ieduK{uVz04?6wTw3k+*? z%}*hY{IC<4JWWDWWy)XN=&kuZ&2xK6>qpA62FL2-`4@_WS^%Hffk zjrca73Mn;qEepX@-$$Km+Q`Wo&pIucg*@-w3C8{p*rk(Y!+=5nZpgRvxCLJpz(YvlN(8Am0}=p#b>WQ`efm;luZvteF57 z$ZsNpTq!g9X9*yHqd>~-oL`TfI!WoWSsBhk!^7vU_eRV?0F-Xd;F}0=!t^Ni-j)YbNA&oy}h=Z%# zdGErqrJ3{mpiw;L&x@dW=h85jiH^KEHqhTz5q15yeEM$lb|cL zJlc0<5(sEAo4JyP%^d5$OS+DY%mGr4UDv#}so(Gvw^8w75em*{i!ZUcZ%F)~@&h0E zU#t|@zX8jM*SuwOgHfmelMXM#pj&} z67EPheVfg$cG6HXlF;o*Phl|AgEBOsOoc&Q#l4wV0-;O}{|;+-cz7MpCulW)V-J7O zb4rWJ%--6P^L#VIV^0OcpTG4j=Ruik;SFlQ8{H-P-bVpCr@>pzDXZ1ZPufD(WBIiU z@k(#Xd_2z-3>TA^&4}|obBz8iP4yetS{Lzr7Ylf~TDMnMa}W>^95B1>g^D#gU)lX# zvgYs+6swgBi-ukKci>MO5;AY_rNj^ss!CX%Dq(sw>WGWZH##+Mo};R-yA-pzuhDrd zf@Tw7cI)3*QE-$iH5*I{TFl44j17=TexLL8`^SMJZcS(^Oq7eKr*TTc>n5E9%wi~P zuvly~AJ@?$_e8CZX`+NC0ZZ;nZxR;>FA?1Yl2P=F7yxCGBMdsvnyJixFBpGikC&4#*e(#R z(yaqe-K&4=+XgFgwtIq>jf_K&Y^y$-sOU4k%H=WhyGyltsI^Fl(GAofQS@cI;&+A@ zvl057we#=Ma$`m?(A>>t>HFuOs}RV^Q+qP<@+Cl+db(=j&S5o^5G487*jP;T^ShY=S={3NTB})c3q0?5dXA z&s%iw^;aQ`?!#~mMibrXLeBay5&ECB(5w$9GRs(JWGgJA;(u6AK`8biix~DHgZono z2O2p*IB^IO@Vb4198=tBATep|MECo9F52%I$YY|oG@Qoov<;@jK+Jp@44fO9qdJ-_ zL&Fkq>7}jSU{W5)uLMi|GlgPzyQMuG;&{3nYzawB67yrd?rJuAycBX8<<&^menARC z{zu^=kj{B1`xDZ`ebjdkCP;{!O6WI2QOl&5c<$;DJUu?`Ekol{Dpy`3{)^aEk<=^<+@KWsMYggshb!F0Ou@}g0{`hXmD3)(bT2qP z-OEbGc2rwxDtCIq?MTZ0C&kCkyQ7q$tGnF`k{{zrnMUqh~fI&2uqV%GOXhJe6-lek=`4!y|+&sy%$(!}mvMO>#L5m|+ZbyJ#zJwL%y8 zra%Y|5o{6UIs!VTZiOrdl^p1HP>gl{%DT;+VEsvQRsGPRiVN8HPP|Y?Pi7L z<#EA!&Dn^l=EVo{U)Q4tuiHJA`Av=>?dL4_oE5o0?si3L#MbJUi97rFqmD;-;`3z+ z*oF8)Z$Y9pmZC;iP<}UKV{XyY;G1KB4*x=W>4sHFz+n?!X@8VI5OQJ% zkfMf9iRs4RGEvf}8nr{O9iEiCPOSo+bBt^I%jxgdI^j_UEoQ^vipQfd&vd+_&F;Tu z){y5wXPg08wldtYhOjcv)5>zIr>hWMt(fcG%?ePj;xa1MI*>3YvgFo(3? zs*=8*ibmd3xt?m(n2LBS$w;nW7ja#l--`AN(ysMSdd|cnoL`8suESyo74uxlvdB*` zjP9=(yL-o~gV1YTMr5TILLv-er`LZR@l!;t)jcMhs@4iOCikU+{PsS_d%-~sWreyo z(1R;jMUe_?RkLw%sKj^o(1W6a?MjyibjZQ9%N6}pl#&?aB@2Q?gb~Rz8Zs_-MG|op z%|z_6IJdG^l8!MWXSK3E(;L%XJ#O*ILd{wtZ)dAw4zI_`n1vHs^<9A%<9PYkohr>nf0sW-ek7oSvR!>bk94*{Cd{cHQ;*xHAGzIL;yV%kXep&6^kYZRB#K4fNET|7ay{)f!e*LV2( zqh$hI%w+qjRpr$WORQI)!k2bPM4z%Y#+NB9P(6n-AR)b*Mi&-EHI=9yAp)aac5abm zDCo!3hdQ9X0gaH!>?Fj3e&h!q^v{Es6$Th-am?Uy!cBS`d#wJ-}^t5^04~-m>iG4@em79yX zm6vz`Kc4sHOL0IlVFF^r>-W*wEm{}1hK0cx_1STWU_8gp91-hUGAuN$Z0;YuaE8Kq z5L5;=qSyQ4n$Ld%lnmp+3Z$L;;sGxSvOG9~LPJhSjx37iGy4uVw5@YW!UZWAXAT?B z3cb0q_dYyOwkPN@y^96y^eRi=T`o-U#AfkGC@-cY1Cz|VKH^5lC=|Z&RBYR}S+Oe>+g7Dw z+eYvF$GD^Wt^1+xxOqylPxd((d!O^IZ>~9ii-0wiY)2yngm#bW zc`;>bHkupkt=DJ10JcLf*<6R#0nZwaBZQ-p@gJ69rp{&u(j96rBFdIi@}@b;b5GFi zZWhow28pb?H$_Ri=8mqMV$qf!GqqY^hdK8uNO<$4;2VxJX$xONk|`Dqgu>mL1J+~U58)6W|p{f zQGc=5Ua}~c%JPImAZf$#NBTcMbVY)wK%vJd)WYxsM2j%*{(A_670PdGV!J>c41YVx zb-^6f?L^?wS<*NXCOup)4$^2bQ^0X#@aX^TYM3n1d}k1`B(3t0<(srP|CWK*A-H-H zj3$Eq3a|I`{vq-s2~c2Tr-6eEGFBu}6RqO7#Q~PXJmx~hktgea z&!yDJuUV`|3L$7^@~w`f)A(7gu5 z2;G%0Y3wIJ%cJq>*;^BK$$=9?R%%=u4Q_CI)G>nTY5SSd4+VI92o)E{36rXh%*^nehCH zLtr6JY!?sZ2ZMwo7Rq~NB4L5(1<8LXm3H?h!uZw@z7da-i%CM_0tNx14}p^XW&}%TLGJZAmWzu{qYqk;lW1;Z?xzvKkG-j!tZ%*a+ z#b3nL>0qrU&x7biUQM6S?Or{t|K_57qVjk7qBl;UZ&SoHu2kLniEjc*JCj_8&^ ztiAKfNTRW6?1exbSEvCM8vpaQ9|-+heHto+pyC(QBS(3UpI)HNPuXW9Eqy=US_Y9p zI0n@{9yKD{=RFgQ9j9Q~C&Z$d9PBcSL5kJm&G9_~>OY2GEEAu3$Xi9GIhOv084ySX`)M1; z{0@`#KY0LX2;v`%3jW{Bktm_q*|NdF!9}dBH#&6!V1#H%w7m^rOsxc3W6ete9jIg< z#j;B_n-ffM4v0-EwLeV)Y51O!bz#tcjW$P2t5P+HeAl<HMHuk#iY8Eh2j0C->q zZ0miB4;r#Se>%_K5cAMv>>3DmvnBsK9?78Ps3qpCm1pw6TY#&O&~+J9D8lxX(wJQv zWy^oe^H18}l(kMyK<|3Oio=pvP(o^;PUV-Nw%$(xGch9G&&uu1lG=fW;|qq2K`c?% z4SFIayJyyRVW0A^h>%ECTUk2_?(r7>;&duXkjaN*ZyoMu4{7jp*@wIw^`4D`s&V;s zoQcGLB}`9T97ZVK%t-Lqrp_cY@H6AwT?oA^01gTb@#T3~24Maew(rX>#rj+B0SWuJ zGK%sA+}wwF;DbFr$BS}+ZC3?X+h}^ON!wD{j^?eU$q#k=u$3w zM29)>4yTjFUmVw4ett@z%VB!HJh2Dn8<~y$fXS`x)g=^y#*ok?94_$1yj<7E0`Nn8 zHJ4Q9oPn)117R9qhwBXrYHk(=7%+?x4z8L&2CgT|YcKhfL;t*fb14d!rQZquEB*A- z%>gc?RSmI;K{%`?I^Oqhy$MsdN8d&+%1bG3!1u@oT!%U^Oewf#1X~SH2 znbk5P)hC~L<*c3cOSA=e>cHEC{aE<%d3|0Xz^9vm9&CbTfK|)h9G+7fSbYY`f%Yem6^92i$X88RUhTq)RhghDV zS%$ztb9&9j`Hl{+Mm#n_w)@w;!`K5>&9Rggao#K8vfd47N&}!l&By=*rq4f+*?yR@B#&KXH1%N)fCv=;332Tj1|$M33IH_XCLy3z zyO~K~@eFEZeFo^UOQXp*3)6&?xJcxS(jKVMSC~x-h36@}Ox61nTILC z%xP*a^sD^+1@ORYjwR~ZTsMjR1ytB{NfX|gkf2N#+}iq-&7Q9JW+|TsadO?ZB^>Yy zKnVC-vFsHgAs{T@0Iq}^$FTB}s%zC&HxmS!?MDvZVVK#Miv1%&!pX^`VLfBx0iFCg z>J_ItQ(agLJOY5o6dclkt~DxKjWK5B#B_>bKQ39QbcNSOE}|H_{w8(juZ~hpv^Yg) z@bwyzv?d$$iMr;AAm=(|U;PPag5-I- zJ(#4QZq<(~L?$lbJPy{MbDQ5_G$D=OueF+gZjF=xAseo3pv6G74hBt$j_(wy za(~s-)y5*zG#|6==M9wQb`{=r^4S2u%jI*k!Re*^iwY?z4cL}QjSDUSj^h83U+a&n z;d3|R#1y+zAJcIBW*;+CiV5rK=_isOJy2fiEK8WG*d?R+N(Q2A$_aTrRO>@Xa0&zJ zj6sdG4EvcFfl;B^VX~AxPwKg{%~D5U5!tRDN%fi=e(5!i?z(p+#(v+95b|&gBS88) zOoVUsct0b{5Wp!j0Qw)Fji{5e^H)mBA*;XmT{c11oLtwGgB=Zf>fC2fsfE!jwno{b zwyd>HnP|c;zN=rLqfkXn$r>}%)9V9wta;3;&$2jD$iPZ|eNg7?*R=P8s%TNF(WcD{*%P*TKyc=F5iz z*zyFh?*H<@Dh@s|Ul6DqjOhQgnWKer5R-*Q&WY~4Nvj6J$Ok{>FArhyg$MRsZV}+c zKu-;6FJ{b_$Z&xLpg-3O8x2O04;I%ZsZ=!rGQU8#?(HayH31e@2@#eR{!hdu)c7&X zWEmtJqmp_$CNCuV*q8L?;`f1koa{r@spk0&D-bgIwpyjoFRa?4V}>lrt~fE|S~8?e z$o|x@FK=&ehd^K>%DOS-ymcprKt`w2D;?NRml9Zz^E_UUS!W^r$;Q^p8l+OulaIz@ zyDcKMbI~3mRa3@%XZh@y_D&mTi*cc~%*-A_zGjN~l3N?}jG>%H)0vB5iCg1h!75U- z)h-CjYf$|Xx2FgW390AzauKQ|KuBvc6^~iYo}MS~k%l1LI;rUrh+;MAHH6qFF4RbW z9skz^}1+TA@-&qR;f%qiTw6 zEtlhcO43&Wa!lfWc7Cw+fFY!W*>b4)(*ZV0eT{e)w>Q2>B1QU9`4eBZ( zXe>6p=CHrVA9Dc8WDd}rioM6Fi2yDWlqaqztr~-wf91d>#p><6wJ^rnoNwuI)v9}*l>jy+ z3C#vm8QjHFiAV+Ltj5HTwz6uiHuw8ZpLJJ9kcnDN}n5qx{x3QR+;g5%K8UNNOR7y@1lU;8U!f#j%^n zXsTII#@E^fJh)6+)x=Scby*})d>f2cH~X`D*g3^z)KiLUYf}vq%DdsFLFsT4<_OV5 zd^sRT*+@~GCEvP$LT>P%Sud2Zra)Ra75FGT3lrCmkFM?@>!oU&7{Tr!i05QkfTi>u zwjd|P6Mk1eo7g%kp9Ci!xJfXlGZ%kp9j$;(&jI$Gxw_Rl9wZ#fC~M0IHH`Y;CA($e z31h&8(f6=(h9^rB{jjAoz>Rt%!qAUTK(_P@fl&g`R=y*u!Ah?H91{Vf!L`gnGBPIg zyBNRP-Qx^&bhK8q?udD6u8MXaV|S~VzvjAx`%J9dwG89b-@gyO%Aftkfd(Qf@!y$v zo}x4+iPKsISUX($B(9C}Svef!!<0l&5Ya?02}`yBix&=GEH#Kw5G<@`7%kJ(p-Vc;{ zS2Aa`Hxhaq*A!Hz_%xVtAWn|JrJFBTqI5XcN0{Py=;%&uF`v;+QDyW6GIx@o^q9Oa z!2MD3a*jW=9OuidSc`o2Hx=nb(6*{K34 z3c+yiZIR_h_`s?fXJ0GO$Do@>`R?wYLS9?Gdno({E;g&YVMZOSbFQ{joz&tu>T)8w z$M+ny{>GDn^)_nz7DacwJ{#JZH6I{Y38sYde6t^qnik&1ga_XnM0dW@CmbvaBof+K z{fkq88f#{X@0$stx2Hp<%H8#*--KXqI3;GTjLGFQNe$U{(>z@WmYkei;$6LSifWh! zly9+r<2b^-?R6)+yPICh_dEr=YPyIYbASJOqyW06LP2ZNb0Oe z>yi;_v)5s)P(W9?mZoPDX|poCDmb_-jn2>ifrKaP0^Ly#)_xLM7}w0-^(iLLEYc3C zTg~S96;cDvR=y>Gbn1Y3=enIbkZs#eoRK~0>xlgg7hnj5G+VC-BGAiz>w0CryV$7d z-b?t{9Z887z&9#MLESH#1A@17&{YN{cn96ory6CB5#SNvy@0fNEDG>FQ9t#}c=-H~ ze4r>%_y-*N{(0=XBgs4?2B3OYc>ezhQgz44mBATW;rY^FCZ9+rMV)`R6q2uz8chxqM0q<2S8M4QHM0Y^`2`55*bKP6pAmGTBX-42*p?wo z%Iu*`1>8h8tv6FAuN(=ov=0|mWvISCqlpL~AarX@hhtC>p|${(K3#kZywZ9ZGfdzW z0Gy%npKt=|6;5bL5gbs{n<@Xd-TiIRob~mjo`A4nt49+_;tFXf)xEGCuhS{iE-Mup zIkRt~M@Dp4_kgvM)qYbS4i>f;pkIysT&_jMz{SG5*rTEu(pQZ)cXXa#vZ?+bwb`~2^RF9Zng zxw<0FLor`Ut95=$@Dgt^LW$gu`75G|hH0~f`2PLli7X?Z?I?g1dKkfiHo00+KX zmE^Ta4F>Npnnc}bxgZ%n%>x9m0XIR~Z=ldaeR_FeG44i2AW}4IIFr5$~7WiLr zq5x2_J{Z3WB}jcI;GX98ea({1epcJgI(E0T3MJ6@XGGmd!n2}^K5)e2!fga-ZuH!* z0n}ZsT;?~I@rA3cFN0QklRA0i6yz4TM@O2>c8O@{d#QtO4lS$eJf4lwC#sgz)FT6D z=OG?+et7U9`zI$g$+QQR|J==pqU%s_ZT$MkS9A=h#=zF-%j0GY1|YmeE4|dfYR`4k1?&mN&3+)hTjXiOjR zj2d4bt8cC9$9y_WmO*dGxg-N*=$kF(H1D_A1N=aRF`Y!5^7U^7gRBAe9`}c5oY1bB zmFy+~@6EVNtDRDzDHlNEOyqC`m_!Lm2boGb(x;K$-A_B|u!$r{_tjI6{N40H6BqhVuDWt^b`&{7SA zJ5?cR8Fpts*NeS9kxy%q6j352-^6mXQt`S`EDr;o)6s0Mu>8YiAVin~h%98MkpQd5 zt+}q0m!SS0@#AHcY!|1SHMoB0n26(g!%2Ro`9$A z@QH?m(ON4c95An&{VHT&shx0sGGxyz`CSIq<@$Wq8$912=4z#xC(>zeKdaxv$J%I` z(_Lym%FaB`0m1sCXE&H-s4Xmm011Cw4KrB>X_@YF!jH>SVR=QV(eA?bJ`HLH->y^Q zk8M5i_V>%HtCXkw7T0yXAmXV6;sRdj^#VB3V`>2iYz)kPP$3E{fTdDs*O?)ddo?!w z@3%8*jj;f@0VD8zVTPeC!3xo5Q*?t(&CI&+^G^z~MG?o|Kc$WL>NZXz{5?OCT=^|+ zfFosbb50i3gCisnMIS$@FaX+S9)3B~MGI|ry{SLcll*#gZ+cf2P}5ReKqK=Xdn$@S zR7`m3$FB{EoINifzuu1UGDz}P16yG2oNr9B`yq%jEG=5diH|B0`MxYt*uE|Z+R1B@ za&_E~0qyA2jl$w`lVh~Ic`ihv0tP8X#zPkAr<#?OA}~V0!j|5LLvi(ugH2~PE|j_Z zA51yku;y&y`?3@FgZe_ zDPfw|QVD^wQ)P*(P}xn(r*rGh9Tk@=Y)CaU-zXR7Bp?)v$vWRY?XHc^O3k>+HDLzR zn=snUdbuE?%qNcKMunI*Yz3{*`K=n0RwH_^G2e_@QV+032w0kbDpanjxC6I9KUTDi zaVa9a$&jdugbrFFc8l}s{ zdZ3WhVo|5}i^ATh3aq(0HT7cPESK9QacHe}#u5r#_B&vRoCCPn@H*?d@OY$AU;!EN zqUQ`qr$JYwAJ^*y$Lr}p*0of{_e-J9(B#~t(N?4Jn1N>d9gIo6P^r-z9)|K{GzylU zQP~xpUzw)=^Zi+$O$qbhAkmLTsQxkl^}8OYI~z0@jnCrMqwrIAf!0C^>%?xYHM~a; zdSuz3BfRt8UR-+9bItd|Ez%_-u@$iYg-m3sWK~qiGJUEHq{h4;ir`~yi8XRRUlrJ` zW?7h5I46iHMJ=57iC7QTcMdG5;rA|HdJiqVm`xo2AYk>={XA9=1o2?8-|WV(Wx_#> zAdAc`CJ};?`q3>5k--Fp`EcQjEpT%PamMKND2`1w(mVj1t+!;g&okSfL@PqZkvR$Y^1XhkvLbpu;sJ6D4@wH!Cu7KknvTq0xm7dq2 zSIGJHkg4422${@y=5WT<$#2&I;W;Ck1L*6RJfAkmL2iJXGM9aQGwcAIh*03~1o2K! z?=K$`nK-mn(SWAFqH3}yct;jIp9}Wc)Hkh@C^Io(3<-hPX-AW@a?Q$H8Ec+f1UF;P z7Er)t)}CX=^{0spW=L%Q0#~^4lfX>56oTerq?%B6EMQYJTr`G&T0L@~f&ir$2!oT? z$@|x^8xJ$4&(ZyFjy5zmFBw@_Y;2)S=J)?1X^Z%&)#%DxbiNQ0P3_S|Q;BClF-I^`U6%sDYY+|f&_jP94XQv8@VMO= z0m8BHEZngeTsa@4V|QCO+5lBvU9G^&nTeD0OAlj2gan3JOen3V^q1-}zQPC=4+ktr zH_o|_zrT?#X(}4u1i(mwl@q45h=t?Ug-Mc#|CIaj|a^`9gq~xAXSx;lwS8fJ77l2lM@|W~IP2E-cW&U@&TF zzyT(2Y#MiPp9%^gpE{R?8Be0|Er#yc~nKtYR88%y!- zvFdjs#==^&JeUDZ}SqMm?!3dFl{$lK!yXQTq6;^u+kocn6J z)Gz!e1^Sv}!Bo_Pn{@N#{rb9mcOBmh_A1(F{N-RxHml(jH5nYoaZQ#2@YXhxix4)_ z-k2|3)U>pbP)G#CF@UPqPqrZ^r~!AZ_-I6;lut^IcG(pdXUd7{VA~(I1wnoF*Y-pJ z@LZSRz&hNlLQ$#aR|7kNV}&@F*6;z}+QG!0vWw+r8+@#PZQ=l`D1PR7acnHIiMFx< z%<(E=$8@Rkho5Vd4ZRJ|;H%{ZFIY-kou74Efc+PtlUeja4><4%%wbZOryI=1eWt;phiChO@R-fnD!~ zX98}0!n`lkUR%qktMqQm?LE=Ca)(FD*j&?ihjUZu{Q{#dD7sxvx#*4aCYvQuJU^}t zXSWfcXU9tETVqM!nqHwjQrzxCINvV8*mf12kvvhu`iPu6YoOfGYbDD9p0nQ?u2!ur zQK|Zi##rPtUIX|jYJ>hNJrfPHTh0#~W`f8JebUjF3a$Dzk>?ar;u(^l6`_fzfpYln z#_i7Oa+Boyd*jShvI+FuAtXE&+*kt>QIssb=Gs?6cGCrTBW;JCDvyZDK8Uoc#4?0`Qz>Fk#FX9_bD@G=^NMbcZnq+$SuZLl zoL}dO48M%zHe?W9;(-rBOQY{;A|~sPhL;}~h(r#UyF6Z^9uN2as@258WK2GrqYNne z%Dy{LHxTY z4EP`+8+)R)oj~cV3yP93Q*yIx_fK&9Gn}>a5HT*XzRxnkN2t55v?%nntKm<&B+3$ErEc!eFSFIk#05 zY#b#aFmSW4>nmlsNWmhaV%$`eGqcb@MMO(GPAleO!u&`J$#gLnDE{A1NB6Q2f-3V! z{;%=OjMJMTTZa+N6R$=RG0>~b_0eYc;DFMr`L)(0b9&|QFKq{ZE?5`uU@X3hJPbQ5 zSV{tF*DEg;ksQ~XL(LX+pPuJ8>%JiXe-#cw{8R{b_#rTJnts^4BwPMK zAh%16oG%u#+LAcy&c~r3Rgn4_S{5MKt$|W&JOKmUF$0Y>b0HR^V6INfIl-`{<21KC z=GGkNpSYQ0B-Ww1V40Ns483su4Tn0me_#eVvqeez6g6Gi1`|xy-;bPZ=iGZH$^M;G z<=45KnH=S8aFG%Q#%kPTwgL3uX}vDzeCznJyv7msa5j&z7*RxF1ND(J z5ATW&SriLFeLXYv*(=AWw3JD@Ljl=UDOERLnx*nY;eP&vOrZUAUQaKiuLZnztv)Dx z$JeCqZ6-(7e?i^BSZ>kit!imnwSkZ2LD{p_*=xCd`P~$@`Fb1LjDB8GvFJaU*BWAE zyj#eC*4$|C_$nrf1ldrfemhBYWIpaQ%APBo`mID!I9Iowq*VLQ@5ync(oXGR@JAOc zrE}}%v&Y@USdE~&H^N896+570zq2Q-OzsgTf86E%fzgB z`F&v^-!=gatt2l`A8YVPRDMv9s^Uvc$92WDG)DL%kyl4=B|9tG7O3}X<54?8b``oy zH!Ra^$MZ33KtwVE;ETcPo$v**&LBAc9jjH+eH}m-W``3k4aC8dGiw-8Koy@7U@!L0laA=DL(?2Am*?hj8 zSOnT)o|iqLtSQZl>O?{}S66ds)_7L>VK(;mHV*II{@|p@l>!!Yjumww1IXUn-TEY$@NU zlqt7045W+-tPBk3*?h#q+^f1uX9CnJJhLU@7NbrY?f zY$|DhE!uf7W=47RsJY6&uMc~#j<48R^@+vIop)Fj{k-0l_-kB3^fv+ZtNyo551voU zhI+XgAJmLGf(oB$v7y7x38h=8*pKW@@0WE3ag(Tvh00h{DbYARsPD#CfeZ?S5%jV1 zcvd3E2iAWLzLJf?!N6b?rQkN(zA12H@GC!oEH_!DMqzmy4;VO%lX7I#)O4k!q*M>> zXyQ1`kj=}6!=wp_N>!ELi@Kd74%FvKj}CCRC#B0!B43me{G$2w^pjpjKVRN8wznYf zew6V6PyXnHZdn>24<;dtzS${PJvYFq@d*{Fv_T7pCJxA)HxgB zm(~VdzqkDw5TVX#|BgQQ7Z~xB2-uUlmG&>ubRNg*=NG*o=rmYXtxQkj**P7)N5?c^ zXliadc-+xH3x-0ALweQ~XNWT4@@fin;q zo!Pzs+KAT1mO=odG;-c^+>pP4adb95eu<&+hX>z4u53P;CmFS4pQhzP8#p4~cWLLN z9EY95^BFSgJukQobr$n~&v)%?)4jZ@>a0O3T^~Mwcvq8)HrR~@Pi}#T;>=mg13EBH zEvz%RsQF9<2S+{f4lg$Rh^bRO0E2_K><84}pEx_5&gN5!&ONu|EK_hNv?cwC6g)Dn zYfRe}XpKyFO)tRIjeNkpaRLEUv89j!Y&$iMuwImFmN_*C-W?*E>{9l1Fqa}O#V0wCG&c(5bM zS-XD34yS{kc+AFO#J#=2@IhEc4lb`7AJJP&1lhu0?vB}rQR;#CP;mWnb01E)1%>Dm za2XWmN+$ZPk~cT?eFm=MnKa^GfTS)O^U=TGp)BPm^;OT}gCF1&=6$3$WiVE#wZdpf zGQ49%{x!Ny85toViZnG6??ZaAYj52U$6V%lwvYUrI6r~uZL)H_Y~c)hyR#F&v}SHC zYOaT`OSeFeY=yfu6=Xq8-tKSlpB6w9sXzBnREemz*P!6f4T{&wZ&`o79MZPeekB}M zQLbG=GFq*wqMMO@ieni}Zk)ohz3M~*tD@2714E*P4Q1FnI8b<(_oBVCJm-06AM}5L1VXhJr5ob*L{TJRiHEw+hvGv+g-` zGt8cb8G(;F9PYBy>R@D(sOzNNP_o2!U@f<770MJ{T3UKlEB#{ro76C!xPh*YZBCi+ z3@k99fAvy*jY*Y$vUui|E#?nCE>GaVg+DZ5-WFNNm2)L}53yrmxH?upNejSWn=;G@ zSFr94dU-s_u<7MjN^w-yJP_@!x2SOHtMIt6hDl?RX)-l-Zl=wDu0&x31*$W2eq|9V zh;e?P#zVkR!G!}wN0B#X1r=5a66{6*D!K`Nh2;GIwukz|UHKQ@;>xrtR)k5$`V|{1 zKSw3MzbkxUMFo`_!TCJYpo(8)`!doBi4DX&HCaU+@Mbj zj!~n2EBXqI0H`Zv|Mr%4$2h9Prp?Kq*V(bQR|Zxk(v8N*!u@9uPpr86Q+oWLw(3yK`fQd1@Wtg}6yJ~c2d&<^WbRITl48NtJeszv z&yB6voS88uH6Di}2G8Am;_jZx<|4{D>{cW8-+{=~Fj^+-W%~f-hX6?U;2hGih9fm| z;Qe>nR88jT1)oo?lry)G8V}N`Oyqrg(VNcrT3pYXl_Pg7Fq!#KH!Uu#HHx>Xm7-D~ z)J!HdHv{AmF{+78ILHUaoE_oMh5`C|J%g$D=UhceNy!8SOHXjr+MuXIIV#$(0-ap7 zs(FWmuZ8!g3yFe4c=E?j@JU%IVp>aHBD}nWx4TYLbR1FbJk};4ha<{KTn)vxeZbmwEE@IQSX#jAe#e&7t)wMX3$Y(xH_-un1g3i0i z;N%4($a=n!dVt`cbeFZR9K$L(9>@6}>05b*KWvf*$uZbJI8PEfx~S4(D`91Ib$B24 zM|pjHVj$D3Du-0~IYMyw?{A71mork)DF9>_eE!>xTVMv~<OYviUifPA_Z78h?gAtk*wsz7*n13s~n2OP9Ip0;Ka z#BQl(?zgvXMqp5LKe^q*p~(2(lR_FgJQa{>{s=ZHk-(^bmua$ji=H*>H;4F#M4f8- zmm$mUU%Q))aN!`yHNMzuBsRS6!hEG#Y-tmuZ|g|DbTBlEpu}@5sRHT;gU%@GodKr> z!nA1+l99yKAonsf7BteSnbbATy+>!@Qhv6@@x1Rch0@Ru5Pvw5!g(`k&Y#+cEcbZx zxrABl@q}KNKBFU(7|&6Ep9bT8)U2@c&pb3t@nTEL4c5ngi7W*YFW2)84Oh&9dj&@#RT0(;0!ekT4F*C6RBf&55+rQdTvvnc?= zKz~@h)!rxH;s6+65$t0O5K;S6ImvYh%M-)czB;4fvziMt;!R|#ac1`?jEcQ}3Ma>^ zh{5}0y1!2UVv{%G?pb9dY)89Dr(e_IXILuk49aMv)*)$`VZ$OsSfnlzO&0+2*SrtW4$M^qT0;uu(Pm4j#VjEEf)OoD&`5*>)GAa2FC2H?$c7QrxCK} z#WF&b%lkkOXg-P_vk>xOi5(~^Nfu9~rMhY?nbA#PTzL>z^>E(T65>TO-j7mCpwH+M zMkx=3=d6w?r8uc`jm}K#b^@=?-7k=|05u-JOK(}&Ox1Hag6a;p>*L%P1A{==NHTva zZREJi>gSW%Pch@PSOL-LJFxU+pG8!@bJ77~29l?R za$goAuw0t`-md6EiUh=rK3%*8#{b*tl&NsHLdcjYVhm-o4ER_e8R(f;@|86AMaEt? z*y*k-4B$ZVf%}V$K-$FYVEPFj7vemdJzf0wy|99*ETPHn?(7kt;^A+bc#OnuQVz8* z*QP2sMx8(>X)%6*53}O=yyoD5bw#6 zAMhYK(mK?#@#{T>O530+3gJNgftA+Tz?ZR2!y+juPB=O z$DQTr-gs<1{tpV@DZHd1!nKOlR>gn(d=>l01q|_m-Q_5?QBhB7tfLc@*ERp*Dsp6t z$xi9m@25dIGqa>M=t|&h2E7P5bhImG!*_-b1wA+yES7^TTU81*JK1P4akmmNRtb!T zV(1q`WlY*{@?c@{1*USglFPMH?gtXsc7hvOpD!NpskWr?O!nIneD!rAR&VgLjntmi zmdh1w7XO-iYO%A~EyY);x)$yNvdEtoY)QUU>HIsekp@v0B`!oz!gBY+v$HWy^nby* zb7^TfN+;A6j8q(Q8Mmyz?WO5yB!Y88;Zzhd;^cQ|?Bp}zOvI+qm<>HL969}C1__Pr zmKjcsY0?=Q4t}H_SI>VfqWVFGrXgw z1P{3zyc_oMay89FRg~$2~76IvO61kCO}lDuw|u{JX=i6 z*Bx#aNJN#5=lXdnP2|n07CRI(8$<^oR?H3*1N?*k3Mt$lAtThVI5<{1c*pbkKS0_Y zIbv-)%W?0B?77ZXn`00;!{=*#cpPu+u`|%xI#N2)awOHRo#>(6hU0pAO?)`{=up!7mHvzSkOOFZ5hZc7tvYOH)wP7?v2k#UfK;ZATnjK>VPa`X&qydJq_L;Rr4i7J!rW2_m)zdU>x#6) z-8FoLoLnE~)2vA^F1K9GKh+~$x&jy5&E;@P8I%Hh%K!ry8^V81M{B`z{Jw1>K7u2Rc_3>U%1r2g z@%W+UI_t*zl|HuOciY^aR9oXkYW94duF!KfkKIu1xqeNU|89j!abYNOq86g-b@h>q z?SfTYq*asSNLli|2CNK16f=rgFcRt7%{RTyfB_}=Md@0oUNn!ize@mjI-q%LwP^#E za%>sMkcKsxX|K%~-YfzEQ2E4y!a{eC7x{drmP<}Nk4R?z*cmF3C13(>3QsOsK{7>= zaJi!meR>HJ4tto#`eM5F%`7#?Ra&co)iZ?rdNH?Vmm}oazs~5_3N^}$*RfmL3(8y` zDc;vkRO61cY_a&6x+-u5;&>bKHDbuMz0g_(YOa@pvI>VC(e_D0BcG4(CKDj=C$a{# z+ubzVR8NH@k&fkIibrVCbl;$}Baf{`Do!VxK|r9wC4>c(ze5ef@{0*UX}N=z32$xr zg@(=sB~htV{~{+N8w*T;#&=lrwp7r4N44zN`}g(vN$HGYf{L6v{-^C!Os|f-Q zB8fXvG)f^wkpfHqvZ~Q~{W(4x5%6xOs^1X+kr@W&1y{#W6#a4Yypxa-KNPIiywj`O z4-VFnn0X6N0sfr`KG$<@n~kQ-Ud=V>g|-xmDo`BdQr?BFZw}mnC$qsFv`I25BV2h{ zz_{Yru6Edx`(JhiVe9|O)TXL;&vo5)9~(xNTAF-eX15QH66N;8 z!I;pZ3EZ(WA9D?pE8LiPyn&uRfJUSLa~Oi*!R=Q7xKCe5NZx_rBy|c$h~8gSGvYX!c+CfYYoH7z2|)gmBIrUhDTse7JqR z@$EztFm`ZfqGt=~dOOoK1P_@G>lcU59-iWTQErqf0Vdg98Qz){qIP9HYOiN1ye@mfA!Ni| z-;vsE|Fuh^&%#VEcuNJ_UB;YC`ta)Ps^Lqec=ZfsqBt03^JI>b^_sxxbDTWi1ssz4 zcHxa(U~{=13IAxZ)fl!nD4j^j75okCjn(6qYsOuY*}iE}KN-2_3f=)`5^y?W%}jt{ zE#P*hSQvs|!*u4GT*OKOFW}mTG!AsNtc@XP+f%5+h>Ey7wby+@7IXI8@RFyM-aTkuQrng6QI-Y;6|??Czwk zf73{k$d{vZQ-*Jb7aKzurWIp(*`=$LgD7V!z@+)}i;AXfhT>MXS@DJ@o(^8@UcRtf zZ&~B;-SyOfU($b#`D}KvfTd*^3itCv^7>I;BNHTW)4+=^#m#(Yp(#@&&;8&Zr#XFL ziN=r98)izhA#|xiDI16JGgl`I2-<`c6#-sQzBsew&qB1<1TbLWlhH*zBfd)55=TpaYhhQ*kPnL*M5rg05bhs=6X4cjCe=knqbie6 zM~UN(^G+~M((bPNN-?=olKLds@FygS3ZaM@izYqRsZ2dVA-;wcrIIxn7RbvzQjVB& zhrVyOR&C)iSZ>}fP-vz%vdrO866y#(0j%EBceeDODLJrHm z!V6IT^YbStCZKw!99Tb$1&GBP3zIYlB@Q`ir9>!e2J@Q;@+ZjInrv9d?mf*~N0JxP zbrU|FW+xsksFKL1qt=SCdmfBN2$T<-(<#SKhlQ-VnnH{d=b?5PBC-z#Ic}Un4Lbr4 z-8_X!`oJeN6O+HwBhg0#?i32p@(2Z(By7;7v^4eAb{8%yzOQu*A(U)(gn|IB2&HR^ z#%_zIOOT&L-UC8=ZOyAhU@718K`Y4fP0#nA6d)F-LCSF^cVZ+flH{RK{Q#@>Io18} zhd!Tyhl90{2Gj!d09x`R?!)x?n9q~gLc9|RGup} { + try { + console.log('api /server_categories called'); + res.json(MCP_SERVER_CATEGORIES); + console.log(`v1/hub/server_categories Served categories at ${new Date().toISOString()}`); + } catch (error) { + console.error('Error serving categories:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + export default router; \ No newline at end of file From 5b2455e758c68c2281f543d570556b8c49d0fddd Mon Sep 17 00:00:00 2001 From: Lei Xu Date: Mon, 14 Apr 2025 15:01:36 +0000 Subject: [PATCH 2/4] added categoreis menu item --- client/src/components/CategoriesDropdown.tsx | 140 ++++++++++ client/src/components/Header.tsx | 253 ++++++++++--------- 2 files changed, 271 insertions(+), 122 deletions(-) create mode 100644 client/src/components/CategoriesDropdown.tsx diff --git a/client/src/components/CategoriesDropdown.tsx b/client/src/components/CategoriesDropdown.tsx new file mode 100644 index 00000000..24d7f940 --- /dev/null +++ b/client/src/components/CategoriesDropdown.tsx @@ -0,0 +1,140 @@ +import { useState, useRef, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { useLanguage } from '../contexts/LanguageContext'; + +type CategoryProps = { + isMobile?: boolean; + onSelectMobile?: () => void; +}; + +export function CategoriesDropdown({ isMobile, onSelectMobile }: CategoryProps) { + const [isCategoriesMenuOpen, setIsCategoriesMenuOpen] = useState(false); + const categoriesMenuRef = useRef(null); + const { t } = useLanguage(); + + const categories = [ + "Browser Automation", + "Cloud Platforms", + "Communication", + "Databases", + "File Systems", + "Knowledge & Memory", + "Location Services", + "Monitoring", + "Search", + "Version Control", + "Integrations", + "Other Tools", + "Developer Tools" + ]; + + // Close the dropdown menu when selecting a category on mobile + const handleCategoryClick = () => { + if (isMobile && onSelectMobile) { + onSelectMobile(); + } + setIsCategoriesMenuOpen(false); + }; + + // Handle clicks outside the menu to close it + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + isCategoriesMenuOpen && + categoriesMenuRef.current && + !categoriesMenuRef.current.contains(event.target as Node) && + !(event.target as Element).closest('button.categories-toggle') + ) { + setIsCategoriesMenuOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isCategoriesMenuOpen]); + + if (isMobile) { + // Mobile version + return ( +
+
{t('nav.categories')}
+
+ {categories.map((category, index) => ( + + {category} + + ))} +
+
+ ); + } + + // Desktop version + return ( +
+ + + {/* Categories dropdown menu */} + {isCategoriesMenuOpen && ( +
+
+ {categories.map((category, index) => ( + + {category} + + ))} +
+
+ )} +
+ ); +} diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 2941ebc6..a4c78383 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -1,6 +1,7 @@ import { useState, useRef, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useLanguage, SupportedLanguage } from '../contexts/LanguageContext'; +import { CategoriesDropdown } from './CategoriesDropdown'; export function Header() { const [isMenuOpen, setIsMenuOpen] = useState(false); @@ -119,133 +120,137 @@ export function Header() { {/* Mobile menu - absolutely positioned for better layout */}
- {/* Language switcher for mobile */} -
-
{t('language.label')} / 语言 / 言語 / Idioma
-
- - - - - - + {/* Language switcher for mobile */} +
+
{t('language.label')} / 语言 / 言語 / Idioma
+
+ + + + + + +
-
- - {/* Mobile menu navigation items */} - - - - - {t('nav.home')} - - - + + + {t('nav.home')} + + + {/* Categories dropdown for mobile */} + + + - - - {t('nav.submit')} - - - + + + {t('nav.submit')} + + - - - {t('nav.docs')} - - - + + + {t('nav.docs')} + + - - - {t('nav.about')} - -
+ + + + {t('nav.about')} + +
{/* Desktop menu */} @@ -268,6 +273,10 @@ export function Header() { {t('nav.home')} + + {/* Categories dropdown for desktop */} + + Date: Mon, 14 Apr 2025 15:21:42 +0000 Subject: [PATCH 3/4] add category icons and translations for multiple languages in CategoriesDropdown --- client/src/components/CategoriesDropdown.tsx | 135 ++++++++++++++++--- client/src/contexts/LanguageContext.tsx | 19 ++- client/src/locale/category-de.json | 15 +++ client/src/locale/category-en.json | 15 +++ client/src/locale/category-es.json | 15 +++ client/src/locale/category-ja.json | 15 +++ client/src/locale/category-zh-hans.json | 15 +++ client/src/locale/category-zh-hant.json | 15 +++ client/src/locale/de.json | 3 +- client/src/locale/en.json | 3 +- client/src/locale/es.json | 3 +- client/src/locale/ja.json | 3 +- client/src/locale/zh-hans.json | 3 +- client/src/locale/zh-hant.json | 3 +- 14 files changed, 228 insertions(+), 34 deletions(-) create mode 100644 client/src/locale/category-de.json create mode 100644 client/src/locale/category-en.json create mode 100644 client/src/locale/category-es.json create mode 100644 client/src/locale/category-ja.json create mode 100644 client/src/locale/category-zh-hans.json create mode 100644 client/src/locale/category-zh-hant.json diff --git a/client/src/components/CategoriesDropdown.tsx b/client/src/components/CategoriesDropdown.tsx index 24d7f940..5e2fe8e8 100644 --- a/client/src/components/CategoriesDropdown.tsx +++ b/client/src/components/CategoriesDropdown.tsx @@ -12,21 +12,92 @@ export function CategoriesDropdown({ isMobile, onSelectMobile }: CategoryProps) const categoriesMenuRef = useRef(null); const { t } = useLanguage(); - const categories = [ - "Browser Automation", - "Cloud Platforms", - "Communication", - "Databases", - "File Systems", - "Knowledge & Memory", - "Location Services", - "Monitoring", - "Search", - "Version Control", - "Integrations", - "Other Tools", - "Developer Tools" + // Category keys for translation and URL slugs + const categoryKeys = [ + "browser-automation", + "cloud-platforms", + "communication", + "databases", + "file-systems", + "knowledge-memory", + "location-services", + "monitoring", + "search", + "version-control", + "integrations", + "other-tools", + "developer-tools" ]; + + // Map category keys to their respective SVG icons + const categoryIcons: Record = { + "browser-automation": ( + + + + ), + "cloud-platforms": ( + + + + ), + "communication": ( + + + + ), + "databases": ( + + + + ), + "file-systems": ( + + + + ), + "knowledge-memory": ( + + + + ), + "location-services": ( + + + + + ), + "monitoring": ( + + + + ), + "search": ( + + + + ), + "version-control": ( + + + + ), + "integrations": ( + + + + ), + "other-tools": ( + + + + ), + "developer-tools": ( + + + + ) + }; // Close the dropdown menu when selecting a category on mobile const handleCategoryClick = () => { @@ -60,16 +131,33 @@ export function CategoriesDropdown({ isMobile, onSelectMobile }: CategoryProps) // Mobile version return (
-
{t('nav.categories')}
+
+ + + + {t('nav.categories')} +
- {categories.map((category, index) => ( + {categoryKeys.map((key, index) => ( - {category} + {categoryIcons[key]} + {t(`category.${key}`)} ))}
@@ -122,14 +210,17 @@ export function CategoriesDropdown({ isMobile, onSelectMobile }: CategoryProps) ref={categoriesMenuRef} >
- {categories.map((category, index) => ( + {categoryKeys.map((key, index) => ( - {category} +
+ {categoryIcons[key]} + {t(`category.${key}`)} +
))}
diff --git a/client/src/contexts/LanguageContext.tsx b/client/src/contexts/LanguageContext.tsx index 3886a7e4..6a643a32 100644 --- a/client/src/contexts/LanguageContext.tsx +++ b/client/src/contexts/LanguageContext.tsx @@ -6,6 +6,13 @@ import jaTranslations from '../locale/ja.json'; import esTranslations from '../locale/es.json'; import deTranslations from '../locale/de.json'; +import enCategoryTranslations from '../locale/category-en.json'; +import zhHansCategoryTranslations from '../locale/category-zh-hans.json'; +import zhHantCategoryTranslations from '../locale/category-zh-hant.json'; +import jaCategoryTranslations from '../locale/category-ja.json'; +import esCategoryTranslations from '../locale/category-es.json'; +import deCategoryTranslations from '../locale/category-de.json'; + // Define the supported languages export type SupportedLanguage = 'en' | 'zh-hans' | 'zh-hant' | 'ja' | 'es' | 'de'; @@ -25,12 +32,12 @@ const LanguageContext = createContext(undefined // Translation dictionary with imported JSON files const translations: Record = { - en: enTranslations, - 'zh-hans': zhHansTranslations, - 'zh-hant': zhHantTranslations, - ja: jaTranslations, - es: esTranslations, - de: deTranslations + en: { ...enTranslations, category: enCategoryTranslations }, + 'zh-hans': { ...zhHansTranslations, category: zhHansCategoryTranslations }, + 'zh-hant': { ...zhHantTranslations, category: zhHantCategoryTranslations }, + ja: { ...jaTranslations, category: jaCategoryTranslations }, + es: { ...esTranslations, category: esCategoryTranslations }, + de: { ...deTranslations, category: deCategoryTranslations } }; // Helper function to detect browser language diff --git a/client/src/locale/category-de.json b/client/src/locale/category-de.json new file mode 100644 index 00000000..4c9f350b --- /dev/null +++ b/client/src/locale/category-de.json @@ -0,0 +1,15 @@ +{ + "browser-automation": "Browser-Automatisierung", + "cloud-platforms": "Cloud-Plattformen", + "communication": "Kommunikation", + "databases": "Datenbanken", + "file-systems": "Dateisysteme", + "knowledge-memory": "Wissen & Speicher", + "location-services": "Ortungsdienste", + "monitoring": "Überwachung", + "search": "Suche", + "version-control": "Versionskontrolle", + "integrations": "Integrationen", + "other-tools": "Andere Werkzeuge", + "developer-tools": "Entwicklertools" +} diff --git a/client/src/locale/category-en.json b/client/src/locale/category-en.json new file mode 100644 index 00000000..a785acb0 --- /dev/null +++ b/client/src/locale/category-en.json @@ -0,0 +1,15 @@ +{ + "browser-automation": "Browser Automation", + "cloud-platforms": "Cloud Platforms", + "communication": "Communication", + "databases": "Databases", + "file-systems": "File Systems", + "knowledge-memory": "Knowledge & Memory", + "location-services": "Location Services", + "monitoring": "Monitoring", + "search": "Search", + "version-control": "Version Control", + "integrations": "Integrations", + "other-tools": "Other Tools", + "developer-tools": "Developer Tools" +} diff --git a/client/src/locale/category-es.json b/client/src/locale/category-es.json new file mode 100644 index 00000000..9aa7d4bd --- /dev/null +++ b/client/src/locale/category-es.json @@ -0,0 +1,15 @@ +{ + "browser-automation": "Automatización de navegador", + "cloud-platforms": "Plataformas en la nube", + "communication": "Comunicación", + "databases": "Bases de datos", + "file-systems": "Sistemas de archivos", + "knowledge-memory": "Conocimiento y memoria", + "location-services": "Servicios de ubicación", + "monitoring": "Monitoreo", + "search": "Búsqueda", + "version-control": "Control de versiones", + "integrations": "Integraciones", + "other-tools": "Otras herramientas", + "developer-tools": "Herramientas para desarrolladores" +} diff --git a/client/src/locale/category-ja.json b/client/src/locale/category-ja.json new file mode 100644 index 00000000..ac6bf105 --- /dev/null +++ b/client/src/locale/category-ja.json @@ -0,0 +1,15 @@ +{ + "browser-automation": "ブラウザ自動化", + "cloud-platforms": "クラウドプラットフォーム", + "communication": "コミュニケーション", + "databases": "データベース", + "file-systems": "ファイルシステム", + "knowledge-memory": "知識とメモリ", + "location-services": "位置情報サービス", + "monitoring": "モニタリング", + "search": "検索", + "version-control": "バージョン管理", + "integrations": "統合", + "other-tools": "その他のツール", + "developer-tools": "開発ツール" +} diff --git a/client/src/locale/category-zh-hans.json b/client/src/locale/category-zh-hans.json new file mode 100644 index 00000000..ef2840ef --- /dev/null +++ b/client/src/locale/category-zh-hans.json @@ -0,0 +1,15 @@ +{ + "browser-automation": "浏览器自动化", + "cloud-platforms": "云平台", + "communication": "通信", + "databases": "数据库", + "file-systems": "文件系统", + "knowledge-memory": "知识与记忆", + "location-services": "位置服务", + "monitoring": "监控", + "search": "搜索", + "version-control": "版本控制", + "integrations": "集成", + "other-tools": "其他工具", + "developer-tools": "开发者工具" +} diff --git a/client/src/locale/category-zh-hant.json b/client/src/locale/category-zh-hant.json new file mode 100644 index 00000000..d9bf251f --- /dev/null +++ b/client/src/locale/category-zh-hant.json @@ -0,0 +1,15 @@ +{ + "browser-automation": "瀏覽器自動化", + "cloud-platforms": "雲平台", + "communication": "通訊", + "databases": "數據庫", + "file-systems": "檔案系統", + "knowledge-memory": "知識與記憶", + "location-services": "位置服務", + "monitoring": "監控", + "search": "搜尋", + "version-control": "版本控制", + "integrations": "整合", + "other-tools": "其他工具", + "developer-tools": "開發者工具" +} diff --git a/client/src/locale/de.json b/client/src/locale/de.json index 56e36df3..0b15b481 100644 --- a/client/src/locale/de.json +++ b/client/src/locale/de.json @@ -4,7 +4,8 @@ "docs": "Dokumentation", "about": "Über uns", "github": "GitHub", - "submit": "Einreichen" + "submit": "Einreichen", + "categories": "Kategorien" }, "language": { "english": "English", diff --git a/client/src/locale/en.json b/client/src/locale/en.json index e0e19f9b..c8914c2d 100644 --- a/client/src/locale/en.json +++ b/client/src/locale/en.json @@ -4,7 +4,8 @@ "docs": "Docs", "about": "About", "github": "GitHub", - "submit": "Submit" + "submit": "Submit", + "categories": "Categories" }, "language": { "english": "English", diff --git a/client/src/locale/es.json b/client/src/locale/es.json index 085e8394..4f9da86b 100644 --- a/client/src/locale/es.json +++ b/client/src/locale/es.json @@ -4,7 +4,8 @@ "docs": "Documentación", "about": "Acerca de", "github": "GitHub", - "submit": "Enviar" + "submit": "Enviar", + "categories": "Categorías" }, "language": { "english": "English", diff --git a/client/src/locale/ja.json b/client/src/locale/ja.json index 5e27001d..b630452a 100644 --- a/client/src/locale/ja.json +++ b/client/src/locale/ja.json @@ -4,7 +4,8 @@ "docs": "ドキュメント", "about": "概要", "github": "GitHub", - "submit": "提出" + "submit": "提出", + "categories": "カテゴリ" }, "language": { "english": "English", diff --git a/client/src/locale/zh-hans.json b/client/src/locale/zh-hans.json index cf8b1fd7..fa0ae8a4 100644 --- a/client/src/locale/zh-hans.json +++ b/client/src/locale/zh-hans.json @@ -4,7 +4,8 @@ "docs": "文档", "about": "关于", "github": "GitHub", - "submit": "提交" + "submit": "提交", + "categories": "分类" }, "language": { "english": "English (英文)", diff --git a/client/src/locale/zh-hant.json b/client/src/locale/zh-hant.json index d1f6caa4..ce2f1aa9 100644 --- a/client/src/locale/zh-hant.json +++ b/client/src/locale/zh-hant.json @@ -4,7 +4,8 @@ "docs": "文檔", "about": "關於", "github": "GitHub", - "submit": "提交" + "submit": "提交", + "categories": "分類" }, "language": { "english": "English (英文)", From b7961fbee7b00b0cdeda8eeb803d85aaeb42b7b0 Mon Sep 17 00:00:00 2001 From: Lei Xu Date: Mon, 14 Apr 2025 15:27:35 +0000 Subject: [PATCH 4/4] fetch categories from API and update category keys to use lowercase slugs --- client/src/components/CategoriesDropdown.tsx | 96 ++++++++++++-------- server/src/lib/mcpCategories.ts | 26 +++--- 2 files changed, 69 insertions(+), 53 deletions(-) diff --git a/client/src/components/CategoriesDropdown.tsx b/client/src/components/CategoriesDropdown.tsx index 5e2fe8e8..8cc42ed6 100644 --- a/client/src/components/CategoriesDropdown.tsx +++ b/client/src/components/CategoriesDropdown.tsx @@ -11,23 +11,31 @@ export function CategoriesDropdown({ isMobile, onSelectMobile }: CategoryProps) const [isCategoriesMenuOpen, setIsCategoriesMenuOpen] = useState(false); const categoriesMenuRef = useRef(null); const { t } = useLanguage(); + const [categoryKeys, setCategoryKeys] = useState([]); + const [isLoading, setIsLoading] = useState(true); - // Category keys for translation and URL slugs - const categoryKeys = [ - "browser-automation", - "cloud-platforms", - "communication", - "databases", - "file-systems", - "knowledge-memory", - "location-services", - "monitoring", - "search", - "version-control", - "integrations", - "other-tools", - "developer-tools" - ]; + // Fetch categories from API + useEffect(() => { + const fetchCategories = async () => { + try { + setIsLoading(true); + const response = await fetch('/v1/hub/server_categories'); + if (!response.ok) { + throw new Error('Failed to fetch categories'); + } + const data = await response.json(); + setCategoryKeys(data); + } catch (error) { + console.error('Error fetching categories:', error); + // Show an empty menu if the API call fails + setCategoryKeys([]); + } finally { + setIsLoading(false); + } + }; + + fetchCategories(); + }, []); // Map category keys to their respective SVG icons const categoryIcons: Record = { @@ -149,17 +157,21 @@ export function CategoriesDropdown({ isMobile, onSelectMobile }: CategoryProps) {t('nav.categories')}
- {categoryKeys.map((key, index) => ( - - {categoryIcons[key]} - {t(`category.${key}`)} - - ))} + {isLoading ? ( +
Loading categories...
+ ) : ( + categoryKeys.map((key, index) => ( + + {categoryIcons[key]} + {t(`category.${key}`)} + + )) + )}
); @@ -210,19 +222,23 @@ export function CategoriesDropdown({ isMobile, onSelectMobile }: CategoryProps) ref={categoriesMenuRef} >
- {categoryKeys.map((key, index) => ( - -
- {categoryIcons[key]} - {t(`category.${key}`)} -
- - ))} + {isLoading ? ( +
Loading categories...
+ ) : ( + categoryKeys.map((key, index) => ( + +
+ {categoryIcons[key]} + {t(`category.${key}`)} +
+ + )) + )}
)} diff --git a/server/src/lib/mcpCategories.ts b/server/src/lib/mcpCategories.ts index de28bb84..3efcc1b3 100644 --- a/server/src/lib/mcpCategories.ts +++ b/server/src/lib/mcpCategories.ts @@ -1,16 +1,16 @@ // MCP server categories export const MCP_SERVER_CATEGORIES = [ - "Browser Automation", - "Cloud Platforms", - "Communication", - "Databases", - "File Systems", - "Knowledge & Memory", - "Location Services", - "Monitoring", - "Search", - "Version Control", - "Integrations", - "Other Tools", - "Developer Tools" + "browser-automation", + "cloud-platforms", + "communication", + "databases", + "file-systems", + "knowledge-memory", + "location-services", + "monitoring", + "search", + "version-control", + "integrations", + "other-tools", + "developer-tools" ];