From 88c7e25f7863a1e1d339e9b9848eb84f62fdbbf0 Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:49:45 +0800 Subject: [PATCH] fix(i18n): add i18n support for custom model feature in ModelSelector (#172) * feat(models): add custom model name input with provider selector - Add custom model input field at bottom of model selector modal - Add provider dropdown to specify target provider for custom model - Track custom models in app store and display with CUSTOM badge - Merge custom model into provider group list - Fix custom provider models being overwritten by API response (keep both) * Upload screenshot * fix(i18n): add i18n support for custom model feature in ModelSelector Replace hardcoded English strings (CUSTOM badge, placeholder, hint) with vue-i18n t() calls and add corresponding translation keys to all 8 locales (en, zh, ja, ko, fr, es, de, pt). Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: toller892 <892@users.noreply.github.com> Co-authored-by: Tony <125938283+toller892@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 --- .github/screenshots/model-selector-custom.png | Bin 0 -> 34964 bytes .../src/components/layout/ModelSelector.vue | 102 +++++++++++++++++- packages/client/src/i18n/locales/de.ts | 3 + packages/client/src/i18n/locales/en.ts | 3 + packages/client/src/i18n/locales/es.ts | 3 + packages/client/src/i18n/locales/fr.ts | 3 + packages/client/src/i18n/locales/ja.ts | 3 + packages/client/src/i18n/locales/ko.ts | 3 + packages/client/src/i18n/locales/pt.ts | 3 + packages/client/src/i18n/locales/zh.ts | 3 + packages/client/src/stores/hermes/app.ts | 9 ++ .../server/src/controllers/hermes/models.ts | 2 +- 12 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 .github/screenshots/model-selector-custom.png diff --git a/.github/screenshots/model-selector-custom.png b/.github/screenshots/model-selector-custom.png new file mode 100644 index 0000000000000000000000000000000000000000..bd2b4b820cd915cee17e769d894661962c47533b GIT binary patch literal 34964 zcmc$`2T+t-w=D{YBn6tFl4&vmN>p+VlCuJWAQ>d*oDmuYBubDB5=10N$s$QWk_bpr z5y?mp0SRw5`~S~5_q=-bZr!?d?=Gu$ceB2)*SFFfbBr-NN>lv~ApsQu1_lP9lA@e8 z1_tH=1_l-j9u|BO>es9f|HE|Cz9WP2W{745-q={`D_N`>&V_IT!x@8jA(pFu!j^9eQ)g zR#)F$UrklS+{xjlsfCl7eug>0s$@iu87{cXSi+7H2$fAp)<_A9FJz z&zrd0i8Jb}X(DBvTrH8eZ}QyaVU!?1B9UUQ7FHtKati;t9NvjD+PJ$ri*R#$d3oLR z;=k$SYR%0nEG*2;!^h3XcLQ47aPx6=H}$^Z=*INVP5yl!IZHQlS6gRyTPH^(`o5-S zP9E;!jEv|n`j7wq>8HD`)qj4Iquak83m%Xg{TFWDn>^hAac{U(4E?Eywxyesy$AaC zcO7lrCHTb78~^u@|MT1axk|&;))M-QzD$DmUzhyX&;EORStkc)S4%gy|Jwe)e)iwn zYuY%uJHc4H+L|jlx?8%!#sB)Ye|PZzyT!lmCB}^&uKzN8|IE$#r!YAZ1Y+F(G0_qP z&x@6ZF)*Ysl;os!y)oA_t#ioV*VSw7MFpIO`?TRVnpF%UVJZ$5JF*vK0y(n z^6-k0TA_S26 zi=SnG(siST3J>XvBnV6rM2HQOl>R`xpwnELVf`E977@?&$(f_mlfN!nKVRJ~wfdMP zK5aA-gTAX2_6#XfQ$1Z^CG_GYyli1JGEtSx*#zyEc&K1JIY~@xN&{AZR^Qn7L3#5R zX0^_H(tat#cC0W{GU!w?jfj>Ji3R_p<#Qf(gdQ-35WR|Iv_{`~K@xjX^Nz19R@oW# zdH(3cK%DJ+pRYr{Rm)aCI~bf)A_3@6V=F+llAl z&JW@M-Fm0_?w$JiK)>Rbm(L$Q75_jjrzeIwg+=A+>G0QDyU#|r{@=Qy?5C zTTK(C3ja(A8K&eVfqECh2V1jm1^o}4HRy_s(SHfeXkW*TWMgYoy#HzJQ}1|BjH-FP zgA&=+R5hDMme|CnWKKOgVb?B^S^|}no0+`k-Ffm)wF^}ZO0-|5m;?$t%{5Qlt)gr@ zJN?`F_!7Tm|9F$f+GJ&>8~$LCMz;D;_@YuW$K5&q-9hr}+WLcKSdzZtzI!%Y^$uU2 zi~D(U!CHO6uEqF4>U?E-vxk)E6q)H%Td75|tPZ^N{kU8+|umw90ok|NCXZ^po5sxtpY%+AXv5}hKl+oIWX@Jg%kS4ddcTu1+nv;Qyz_xr@;DA#Wvoz5 zr$}A=VS6yET6#lz$@w6Pkw~S)KMbc3uKS|Dy3R4*n-Fiby6;*d!5AsTEz=)4AB|F75)w z{HF)Pp@lvD$(&?&$mAsd%(!KcwlkK?KOr}$cg(yMh@I{lf_3pV6~W_6*Sc|RZzn&K zJZ(7P5Z5ol6R;e;zF1zSoIqjs`3=i=`%juuwevw|Cm$uxj?=8Oq_;Wmz1GiHh|zwj z%rN80pDYhYN&7XdeWCP%Na5cvO}p%mxrYm)`-p;e2LRooxI#1Zpe-;n z9UGr?a*d2twfA}&VSZL@>Ap^$Jy%p{8AisKE-Y!a7ZV=*^h(e8Ec+`yKKre}H%l`> z<3BB0^ES5%eIjAwW1d|SxL`M&gPAAhvt!Yk9lAd8>h1^L`(#AbQ}2x1+X zyvnKqYl9n~S}NS$g%Rb%5RW&W{h-fk!I~9bq@9bsiexy%n0|G*h?Lf9vVc z&l>JUu?Dk+UN9BoX#+TsUjk^2)9);3C&>*TZ1;1P*Vs=bVA(~yaf~lGU9bMw>K#{dk=k(`^-}TJaKwa&uH&vIM6H}Om4+<8Sr_^igI~ypreB^-sGpL)Z7vBJb~C) zju0!Y*aGJ!_Z9tkH&Ls)smdVA--}-SiiXvhhnZe;(y}qUgCol(K_|)Y6+FLJUMPn+ zw;zu4<+hFb)r+}B_|z+!)_?DbjS-6{25gSLy!3#Ok}rG=Mx@&B^Q^UB6+N_Oj*| zp+Cq&LEL!K{>l*j-6(W)vA}=TavnxW;0<}m-Fo!R5e5*DdP#Nt&YLm98+NQxNAxO3 zRzXaOXDQx4Zzd0Kw6O2ZJ_&{hV*z1^Nkwzxyx9{x#hiS+*G*T@k-nESPU_C{oav|N zX5=LBhD6_k6FuhfEXa^pF6r89=w|GU@P@Hs^}MHy=s;!=`iL9dOiK>l$Upp4cHUET z?5o25bRIhX4?a-WZ@)H(ZA}ml244s|&9`p+8Tyoaz9ET2ytluxD8?b-yoG6cMo@ly zeOPg~(d}o!2d(Xl@gjI`1gX@f&>J}8_Fk%IPH*vT2!==m9PPp3gKP*oJN@w?#c8Ht z``6cZ(s;Tz0TiHu+wd#~(`#W}UE$E}P2>NGUH~)&Dk(!InPbm_u&1?uLL__*;3@Fr z$fv48^q8JMsP6sy= zBBEd#zxC& zTpmQZXD=6Dop-V;mi9$EIi?)W%9vuFiO93pQe+Z|Ol(`KR{3yx25u{5!oh-)>LroN zkC*O3D9KTrd<(L;m`>|)(D4fCV_itnc2J-Wi&G9sj*kpOF=SDjMjs;>IZIyO|Ay^% zn?vsnjgQlK7<#0`Z(>>?c)Nm_qT}fm;T{j^*e11##Hr(hC|0H61_f{fE5AyT?%roC zRamq>#Q$7|3O2+V;TVPyvCKwhJ3iRfV>y8dJQ>OqJzf2kdqN}TJ-i;YXW;{2=X=jH zzou8d=bKZdUD8C?ASRW;jfVp)*pXZhjz z4aXT9s#%DJ-nB|;d@qX_*1yz0vt`mLRL#1qd%lX_VCN}yYN`fm!vV(X@~uny)zusK z!GCUIyfFnZ~X6-Xg^M!_HK7YW8z-YUr#k36I|)f zmTW7TuQcyj8hmkhC85T4OyY1ckyYa4XLbdiuNg0JCa;UkGgGzcyEJItBlNbXk+w{=<*?w3LA6!oVw2f>f`0!O5&)%CsTUa=X2&!nWBB#I zQF}43Y3$MnyC!km<$$0!zWbhGHR>s)%2bu*hWPi7&uW3Q%Tn1H|IXL_TZ*XtR z&8&z0KNbNzbRAsaaS{7bA^%- z30-Oui+4!Ol5JEO5i=)yeuM>nFGJiQPhXs2y(48vUHN;4AQ=4`1)V%O{CfC-o}MACluZ@tz46+pk+kKuwvCyweS-y~ z9be-j0p81ay*GI)&&OCV*{f@*7R)zUa8C}d|87m$9-I@Vb=B+>oW4HU_SsQTcCYMf z97Uz;x20BgJ|`UJ;XXV58}Xki=WzVJRG*`lVz`t$o&dv*HIL>U!%Jpz`M+3ZLjp-c z5(=eu*1HT8@<~JTM?ZXwjhjHCZkJrKCV3t`{LhKMclAd zjd0={Ok2_2F3Ix3ax=>ZfFB+czKYoyeZqz5CnhFN;W}k;Sur{%L2im`PdI!|N7iI@ zJEl2uJ4W@x=t~tPZKgsRxg$7i@=5`0)LYDW1O80ORYywb|Y0W2r* zn=4aG!>I2nDxT>M3rNlu4Mq)XILBTl1-Rxvbd}-W5GXPtd+k^qvw+9JICDXjX22v zo7G>~h@N^g@{rzB`74REtmTg!q<2G~{g&!fIGqSy^E&R`VD#*L-Xg61fqt;o+HXT| z!;*_c&M$`k1MAlMYJ_3-U_jLC!K>u;j)*O{Ngdwhsw)wG9B((2lVO%2(RW`NmCI}B z`s>b2yZg>WS6a#?bUDGHllV7|`RT8+QjxJZVb z``6&uU~bWJLkAl?Ev#cxCx~uxq$fLiEng7^A%&Ml`~6$@r|tYz5HVI%_*$*5hUg%{ zsDQB|ixDrKPOTKJFBy9;5Mi@Ku$%mYh*qRRIM=wvcm3?Q<>2twC+g=tsKG@9LC;=T)^p5L^Jol zmCe&GtrRH6cW!LxUWakv#mXg@zGEN$hy@LE8I+;3dBp0D=*~()XAdOJV>F?&a_~g5 zdmViau-bmH0Y7wR{h9-vJ8mY_^sy+->fHVEktx; z-aCs*^IL(xSMQ!ad0Tq&d+jrAGQhVM|HG>M(Xs2@^^UWyMlC^Sty>n(!Rj>P%aBSU zeet?tvcAaue1J#-9uWdKyH=hYCBM~c;86e^iwpBOOxM7c>v?kJQW#{~0o(7dWpM%F zAmBJ7NNF#?pIkT6{@E3S@bTH2$r5sR+@+55-a4aZ~&4bq`r+t zC3@UO4Lr^Z?LbOtIZjo*r0fJ_=gb7q?s6Gq2>{zGj!Y8(*W6heNCT3|j^pQ0=3h9c zXB*v$0M`R($2rky17^*)!R5O`l|>(rGvX1dEM95ATYh{0bmiB}6l1FCSFt?vW#=#SCyG*I+Mqf^*e$Q%T?Api_4k2;?eCYa-Be9G{9cqDxC;E7 zj!c%A&!bPTl6g!!FW?gGt$il`p_afLwF2jR+JM5nGvGBc0iLmC`^ykI=)EyAs5Fx= z@C1@q+;3klvqPp4Iz(}qUV_GwTelb?p)a2E9Y9Rr-;Mf@*^=`>DKW}FnfzNnpo>mL zMX)f{T{1+yV)4i^!Cm8a}fS$@d!{)WHx$Bc^|s5sgp z^-Sfijgoqxx$=jM!XJcYX=P);{|5q+J;J&wFms zpWi&N6Ok*#-vFvx^(7JP&ShrA!;mEw5m=4%fd>puQ@yyI;r{D0jbpJGWzz3alrXfj z4KC(bQZjmsYW8>!l`N4Oc0{IHtt`V0{zxN<u(ad-SXL4ymy+8RWmq#h1*!n&rnC9T&X#A2f(IM zW5aYYP?AVxd~Bij(s{wCR8@8#oVgN84hFg>3L)W;N%&m4%7s#m8=&R4y3BpRPGojR z=J~ZVNK)wd7fXQgRMbf0v($7J zd^NreSUpYT0*g+_Z4y4Rc?SAkUaTj)l%w!i>FROBi9GV`_*Y80MxPuA$&V=dj+dLv zho>>isy=TQ_?almIINgl!cedB3Oj~LfR&^E5+hC_83BsvaOSqdbn3mOF&AzfrAfOh z(O-<4SS+KLMsJh`)i5r$d2P{6(Jj$+zsJfsVF_-0vXHK#A8G@6Ml)t=DwRrCQVRaM zLEs`Xpo_~!H&rpsUocPOC+ympItRni0;QP5H`xUZ)rx^bKZz8t zK#B~5kRJ=AB(-*l(ZxNW&mh#&U& z!Yei9NImSGUaPkk_0Vj6Q`l!LerGZ)rcEO=($KGPkl{6@1y(F>gyTm`Gb|Tcr#fpc z$zz{dX$z##WDiRXs{H`sw%1~bXBa3eR0p!M|Fm}Gb?|M0M!q)Ye zLf>H+FyKGbC05%@_#{6v`CLk0Ee^#Jc@3qCF-fNN4WrS1i&0&2fM$Qt!FW@?HFW$2 z$<2^geI5-SVa3_($pQA0`g43v45$KDP18bbD^{}3YO>fdNaBaDbkEQ-uyUFaxk@lU z*0g>znxWq7_fVl%gSBUdHibQ|o#C%ZqpPKkEgnV(`9T3)&t;L$psO4)uc`e4Adbrs zhe#<5N5>5|V?VQxVCt$iuGg|iLKU#>bo~_-dCc-8EI3bYom;_DlSacpOU5b|$49 zN%=U;5#M&ij=;V&C-~>0#+=Vj+Ctw1kREuFOoqIjhov+({m{L7fFxiQxW2q8yV5a&J&B&MwIYe zCqSZr8Vy@E?Y)9V{b*6|i)}F%x{)-R<&-0qeRKXbnZel8>av#SsR5dSMfy^>%A@f% zTGjKU;y5Sk5J0Blj=YQGM0fikAj7&b^!S6Mh9-JZbP+}*&`V5FO#GV6i2n0ulY`5p z_jnec{Qo=((*EMnbl5uyUG5*i1I)JgeSR8pl92xs&NFRu!C%^~Kw3f^ICQUV3_NMd z-vKVF_0RfW1W38QfA<}FT7+KCm6^YJ{t9}4qQjEjyJLPLv;bk$xWSphxY^rww4elT z41$@MN9@gK6uf2tjszWNiuKBkKfSsuZlE0mK_NTnl=AV*MVl<2T|ZO-VczqswNzdlt6EM7_L!M95sEySk1aSQ-BCl9h4l zpJ@V|k`(m_mxxBleOXhd(R1T|Qp$@Vpn*Sdw+Wo6LOKUftI$5cm$DNyJdz`?`GC(9 z{2gf4k?)U8L)jaNzw7*R8NQB39k}$)CZ%rZg-nC3iEpegPP;VUkgwFG9(4F41*1p^ zXwb+|_sy9!yiFj#?&xa~c6Ca)paskS5qF6PzVHf4ie+~N{g^&rk7N+jnhFTd3Gynd zLFGvl9!OLvFipE%WEu!9#$n(Eyk@2yeHPj=k4lUht4dS>K6-cn{|K7t8fYSP_mkf| zZuZ$NRL@NJoxaMY?+hS0>CgHUnW*Qw%wvM>W}shWh?kj8lei2Ld_#YbvMA@gGIpiS zija!10QnSP79WY=ALVR`fT0ZG>s>L_^)@4UutIYEmj)=q(L7ty(<_|aF{qjAYv{=m z3c1w#?=Ilh)b3QUALbj!ejuAO z_EN=~L(LEyR!t+EkFOrQz3pqUHEe7ZfvjvV=y{OkzoVp%<|o3r02;rGBzF4zhF0tF z>$}LZQapiV?v_2$DN>`VRq-nb-#ptrsxTlvzYAZ?Gk0Z0G(bMn!XS`pMargb&q2v# zi!k$HD%bU;>ZA`k)_SRYr8Kuiqrc!50Xe%A=7n$W3Dil4U6@s}MCO?gHn78`f-m~Q zQiYAcmU%y4fsp^ysL^#}zAc+?<^Eg){&H`cRd1Z>%IHe~a?+>0Y;kc9D$OoB5vFb% zl-;jg>`P1mo>{97nKx~97)w+l=JE}Dh3KzosHMoAO*Yk3DVeJ!d?6ftW^Y+6<>G8; z83NZUdwl-nych%B^!M^eSPG8zWI>r)wqvC8-m=yb1`R(UZ8l1@NyAx9 zJfD>wO=s^Mg_;jnh%VR^>zo7U1>Txy95zjHEbBb;&BrylI^?LUeY%Jq5@6J$_z*05mD@Pu9`MA( zb}n7jz0acrqxxz6I9wNdLkRzvIm8`b5KkOKMn3chEeP=|RPA?TBT%ln2(RfN5g~Tv7=y7vpr$y1WTXNq*=t{z7LF=Pru`iy||ISFLm% zFy-$#C{yD2nI5l~JJ-s6+kW2615o5@MW`7cb9WZ92430?A)iAYG8=dlVeU2er6ot;ihky=2ACg2yd@zxshbBuT;4Os+k zr=h-(8r)XN3A4~boy62Tigs8hj9bJh_3~i*zKiZ)T((M~oBKwrkMdFS+_jRrN2NkE z#-KgKLKXUyH~o6g1_E*cDZcQqFCz>W$I}d)+k-Kge{?u*P1w^ESYN$@Kiab3DthxS z8k3QrG1=uPYyr$!IHNR`$b_|{x^Eaar!J%j#e0vTu8l@9GYBk_A;_yLDHyrLTz_q@BL-5qqbgPdvEuXWlBlm+M+n3TCDl&e2bX=Pw7c5KX zw2)+j0>1#Lz3^4A7~d_cpEL9O4Oylw9EOH(cJ4_VX@m^C{GoBEOn($$3{{qUw zd%2EQ;*fa0$H)^Jca=b#mcsT&tB?{YQS$olQ>BZdc{St$wpp%OhzHgKsb{FIN&6mS zJmdP71KF9#ZiXMMhiiQa(hMmDOk5*>ENoW?MW|OUZdn=0|Kar<98J%tP5u;MBT00j z>AmT0ElCec&-FyIu2k1lp7j)e`p}l-yM>1C+3t0B^Fskh7-BVZzVP6#*E@`iPH$}4 zD^3m%!;H<@rg-oxK&&|7n!v;Xev1;5ei8AFgPW?TR^VTQ!e34uDAczW^LJ+rz#R># zvYI^XUV|zgvdnJWiA512Thb<9WCxzxe?iu{cLWX>j^P2~ay#9}& zqW{YP4^f8|u+d>k|A_emxVPpiOvAP^XIlJHB-=M1X`BEwqxlW+>1EKJmqj}1V^Kw= z_fAg!{^2%m?k&1&PB7lytyO|PN0H13xgddwXYRiH>povQAA@@RQYo>}akdE}`|h}K zRfFq~{!CC4uH{bo8=1AcaSbFRJizw0EUx)dX+^zBh~TH?g#K) zJXU~tei%cjyngTXB(+mB37dKbAQ?hBvDBgK->rdc(kW03*#-`*1q4{&z_bFtMX>?W zkimRF2hKu(A|Qinf&2{UE#2`DcAgdZXZWm#&>5>bJe)Xib$FVf!=n+Osvb}b?Y!x` zxa0HSYXVNsK)e`@;O((Hb#e@AA0^**xUbI*lpQF$aLINTZKD`izaZ8BTxR&QVhXg= zb8b;0YpE}>2+mA=GFCC?1*x{cleq89N(s*SAddzBYI7fb>*d560lWjCt$O%S*lkI! zHVp%zne=()V#y8N;<%Gokcfrh*{_-SR{}b24fqR~h6hYk-4B5?yO)*@sxIGrfqEv@ z3fe!n5*daH1Q0i1T+FY}4(!m(TpJprOUonMuM;g6!Y#mTrSP9@Sgb162>X5fTLJ)w z|3@Ay^nPyVIU;*TFX^~I?gmE}tqI1xOX19QhdnTeIRnmA) zlVuU5Bh<+3fT&87kSa)+ju&fzJyGHBI_>laR{V$MygwvQ5Av0hITqR1Q#3a~LH9r2?zu=n z{&2d&WJg!RXNTE^lP?Cdn&kR5u!^#5yY8Fjjq6DfA_Xyz~CMgqf+zV;2Zb zcNP;hT$RNc0fy=)P~4OC3+b~38gKblGI%iRN57Hhlv&hdCikP)85Y+tG(*a(u+~5* z$01~(+uBM}$^}4w_2O3+aoLE=Ko7Gx1vk+`J^_mZtYbVQ^x2T&$oj(Mhp`*_Yi+>J z9nByrVQx3t4c?|awFX@Tn4I3)C| z=eHW@O<=j?RggdCv z-?axwY{R1cPGFa#G?e5J-29k`_Vf&VN1nS6l_o-msneJW_eVEUdChH5g)fHUb<>^W zMoq0cP1g!l@fCd_N7L68NRCd&y3KxzSCrjmXayX2VT2^Sp^gGn_^dx2Wjv0hLw(YC zGI?U+X;%S}3BR}AnQf+-dFxefHJH9At{LQ}gl5a76_IgRYDQ5OIU}!aa?4Pcf)y+B zMhcPvXS>uqd#89MUGbOV;&QN!fNV&Ct+d8r`L zo2*hI=u|9SWudQQytCk2$rljo%~fMNcjB&xD7!8qR=827_3JQ-LbcwXo2W<4L;r-kI|iVm-#cp(?b7Q0M-NNBEr z7iE=(oJ|xbjmC0K#j?VqF`N)Wsk$VD8aRI^HMqvLO*!9O`~_wO|AAqRjpf%A8p2 z&x|QX$yEMYsR|ht-1>L(ZPq}sbwEPKg^sF9+wo$OC?eO6g48KgXDNnq ze@lb%o3NyzU%0qMC<$LRqX7xA0fbEja?dKW8Xk1A+AeOGPEGKYgX7l&=J{Aub;r=S z?6VQA)9oI5A4}7F4?F$#r>tqq+`$|O(_ogia5GpUURi+;h21w$XZJa_{JytK#6!fa zbak^_xq!9ix;FB91pRN)xrDpOs=)Dx?yTi2J}+^%1}U!#GK$MEb}A>Nvi5RL z588ii!L8RztG_Je>ui^UwgPTpqgH{^GUQL{@%`#)x8V0QmS*7^5f#dOFb zks?=rV9~~DUHmAU%myEs$|8?GaXq>ya@0e1`;5~Dh&S|}PQP^nKEpfgXE`Uzt^iS{ z>7%`a2EX0FjrBC#d4q{oQwe~LD;B$ch!$PH0|93~!}K{At$RKOo=i6@=@=wUlnZv} zy_|B$5J7e{$wD(-;Okzlu;3?5-oJ4xwalb#&UER=9$*yk0lMA-pB&u-;Z=~KzTID` zs8p{kAX^*P>F~hoPVlAg^IkPLk9D%}(5tlKVU?Ss`FEZH`2#G4ASgc|(3qVbAC{@a zqFF7tA=(KPeOOuxn$7zKH1EZ4rFwj@eH~KtLalrS{|{tZe^-jKaR{jvmpe9Az!-Fmr>t(?_rA1pE*jx@B}|4G-@&*4OL9Flg#2H)&s!%%ka_t+`n;ukl^*j)R+U zKZ=Y^;$&|Oj4b&^;Np_`Hdde{`%uLa5a?6#>)%Rn>wg3OECsc;GaUcV*8CY*>XW$) zNGm1$4}J}#@x$5gmcF+>9Rfb5Y6OC@3+JzSXbK5E&tBJK+l`=V}`VGAR6qLxOVGO2m+Oe&+vsRF59|8Ha3kL;LE5vt(FloXkyTFX+x&tehcL-9N zs%-vHQnlp=1=N%MG~dq}x)dITS*+m$y#kht$-V?~6R3qchcB-qs;vee0b2wptaP+i zv)S8aJnaar%0_@Ut8kzJ(56DLKS<#+OF;4Hq%p|ijHh8KtChlZY3;j9+x-d;?83B5)$e=NoWj}dRyq8;nCy;QEK&WSl z377x!6zSCjwh~|dPBg_f)FSL}&WZwg14#aTgzkDr7;Ze9Z+k?FSLs&(HK(V4w}9FL z^n7ijdiBAj-X9Q5fTctg=bCt4NTRha7EHFl0Xj5Bb?FXxv;BW&dZo56cj=CX(;bAPi#1Z`itkte3{<1*eq_Tv z8Hr(-!K>#Z9J~*xZ@n8$eq&z&E^lw5%rM(;Q%Y`Hl|Oh}M=1k2j*=PAHsZZuBBt7MNV9OsBUjc!+#NcMN>|###^1K|Si+8TTKC=< zDII7$$q<@uM(KBR0OyZ=1=A_lQdjf|U)9GDFMmfl<&cI{8CYom>W36AT2RR|NydRq z!acDf{vm?pmN7#*tBbU|s-_Z4=;NdprT$gH-O_BmGr^b_8c28>Jd&}zaP&j~9NvB<7(~3I`0y=lqpITrmq9V5U~4YdJgk zitc^Xtvc#CqI>$JFhNU4DpP9}q;@>=Nw?Sfm7y9-9N3vy1W&Q6XRKvB6hg4wbjG60 z@xY7V2O5807}@*JukKz--E%t30g6DXf&?^0G>z5wP>`DQJ|5y57A^L*7zG2x&y3~k z7Ca0*((K)Gu|%PVnld|^oPb#_i3M<}Z0W$2*G#?ZL&I+1ZD zzVTo}PJn6LLVSyjg}H$^({lu|lvszqH zeYFT7e9wvWqPzN|&`*kmh|Tw=w=67){j}TRuf0wWs;(&_5dm$0WTNAjmabk75ssk9 zNCzu8Q?h`m)zQ)mCs=y8PtImmI$Gi$m^r-pCYI)o(brKh#uw9)()~0}apChvNQsAUq~W?n)vA&HcqYD2aouYFIG#>gY3f^3*;l#rMLPl1ycA(cwJ{+-Q^5*d{f2Ko5wG1CF5wW`inZ9;@Z2~cvQm3`KO`bh;2GOuRy_^c{dEOvq})pD=5WrlQ?1ASyU)?tQ?3jekg1vcUh0uN z{3Nn#-jll{wg_bnP#+nbP4QiDRwyr9A!(jeYYF!@%izeFjmI76eH(&@Q32Jbp~W~6 zxH8tGo_B1eZB3sYK7dvz(}LqyZ1#B`UA%7_V-uLslnIB#I1~wt)D*AH*ZyR`X7kzb z)p^mIOp=s7evS4=hNGZdv_Z{Tt-dptIJ)C2Ebb*S>r8P&5Eil@i#jKv_c5 zR}jrzu2>!e`Eak?cu;HvJUhp^2@-z4SCfTY%=(Y<-Bu4h&!G53C7HuAK)eLt6)0ex z3vl=WE|JJi`n$hj2q1!B>7j%-c-tCh+e0pt9PWQ{3W5_ETCu66*8s2Yu{xfc;D|nU zK)^L=_O1lB4+JXIH8utcTp+e z6bNewyl=^UA6b?Mo&5e>s#_A;2`H})@H5a9i+~FOumBc~@od}friMceI48e|%ap)J z#6jT-S_XBTIf%lITp_q_EmG7G%;N%_v@n{DQty7@Lpw^Ni3~I=(Lri+kJ4T#{ z-P8tD4P^FTRiBVwhZx!z@YhqJr#z<_L{>mkuT0unKgj_-91@2_ERf@JMEHvVV~j>= zBpCT#E&)g>Z#`NCv}re9EWL}?d?OSx zumAj*9R!#fRbJwCA{KbuKq88zdOuUlXLK?y^8iY0;(O6mh)c$A97?|a(a8!tvMor| zZvgw;+C&+DBT?uNP&{3e(*dfdkuaPT^OvE^S94`93XD5s>ym;({bwFs!)96Yf}J8) zup0oKi1Px*pQ=E^rx;UmKcGs8HFmDXmhAaV{534OY?XzJnYM2<;4u-5;3Y%ntnu$* zP?5DTta3^K%bzk&;e%Qnse17%!`6x-ziad&MccbRLLsZG@2>@E z-wJwg_~m68=AW|v1!X1MclA2w$2g9=uTqSCqe}hLHN7^bYxkFu%8H){Dqc;TKY)U$ z>u$(ccX`jp>)80XJ*13@(r|8|oGY1+SUE#u6P4%c9H@mUIijZe(s)jeVX3O}K=D_j z>koq8GvpG(IqO2mu-3#nh4jWQRoTl!To=g{R-M~N?xAB@hY~VQ*k*@%nJS*!26c7N zPh^Bq34d-oUUD(nKG;B(iB&!f;r=e(%RjLJDFXT}j| ze;<9XtE|A6#OC>urYu)urV2!OHRbXRSsP|X@**5_88XmyQ1bZmF9PF4LGl?6$=)*%SU3* zqH?}`lH4J+`f@?Wpw8YvMxAMReT_#vE;EO$e=$keXsc5!L=V_gs2s}jxH2n;LYu}2 zA5hX3HA4-P+*J<+=Nsmw!8czBh#aZ6K6dAStE~BmoHA3sFijFKvC_Jgq0s6K|58K- zxQtV$h&JkM5W&^1y&ZSuvTY+m(e}(?Mno?;ZhG(iH1Jgw*R+#(fU!OamGIdoftdA< zbg!y$st^@_-UETpRbJ!>?t;P8+=S)yD^?<0!Yrtz31SW1J168QGVGk(p7_e~rv z0=>K)XLAIZcZoqY>s#q*@Hba=FAhez^@*j6Dec1QQUml8wCKC=;`6Z;SU7{^oQpx*9GtT#BoD{Hpw1>g&gU zhN6BWNIGTB6bcYZzs;VlliKbSK6CWtHP_rzl2dxq{xr=4UyaR%rS#z^%^b4UZkb2# z`>7|(kF>2y5Rzq&?`WtYT$+E_vC7R5QR93V66RiAuc*&X=qdUFhTZK#vvNJOZH*7x zTd+1y>2n||=0I@mK79xkDSpUVkxDFu$Nq?4O*a?5Z~YzZ7KFRQQ=z7Nqxh>!_qY5A z$)dQsnZGF|G}J~%Ur@c=TH)$vf>w~RFN8=f<5zdJByEHySV`v1iXKW{vO=5_hU|iWlbJCa_P!ia5$$qYlrjIGy zCjW?+rVl*GL46beA-p9r8Vu;+B39dzjW05EqHy9*ox)pvlI2u9?A z89Q+5LI6_+kqvz9uk^}?q-gf%G{nCift%*f#+PfH_qfs}x4X!|vXvYB|Mt>tWD-;I zSwQzeG384JR^j_eDR=8+TTsw^YXCq8kc$+|+YwNQ>qyD;;{wiQZ(!;xYXXTcje8!R zLe@NBZ&9la^}z5`5Fno{&$mgEKJ+4CXaJoXXg4U$cx>UmHJ2GYLV*}P1$d0^>UhZ* z+*kD;1F(d}{5Xmlz%AC>Pnm5EYB_#-btS|N#5%R&<%UO+gD@8G$SO(frsV=Z;CCTf zKseufxP?u*fyVzgAK)=Oc%gsm@>i(rWqM+*Nlp>#(N_zFB2r!Pjo}*6_*#|H@q;sQ zOm~RB`6e(gdh%3|A`ukgzBP7Eg-h|y@vaQ%OP|6GI6z$O`cOz#7z1)MuZBMMK^b?J z9*oujuR`hQ=DUA9@I@vcs`#}Gu>HX_ftUWreeMtNrw%kMPtayA;U}Pk3V_lIHJ2It zD`RYS6R(>WcJo@`)DxWZ4pWTzvj)s=3dh}k37JRWGJ9C=k`!=uLyyzUV=wsFrB4-q zUj@${;3yHjgsM6Q;qL-D>|9WqM?ld-qs)w4yM1+6{Xk1Ml1A&Zb z3`~}Sl=`72W+DoY@iu|mzB#POHl@87UD_+6w-Lrh{E#$%U%9`z=xuj1zRJnzms+Z0yW(-xtsv!y&+Zzxs8k#`9^O%6W$L=cN9ny@nUsx zDyv;GsdS{AV~kUEbT4!~8Y?7Z5WWS0nt}4s%`}bhzA~vqDQA5%6M#zfSmM@0G)u(B zzoLxOBvI@nx7_ZKPtBk_BnxS3rvEr@1Rm+LzB4y-Ha`P=n>c|%K8imIVO;0q9I;<| zDpjY0`BuC9%MPNuNk?!96>?%(s81PoV&M0YxRddbosy|#i+DV#r$Gi|Y0P}%`}55W zSoCL2skXIurLoO~LZk=;w62JBzV(Ei9Ca}K80>plKRfpElR`82EW4(CWgK^3;IXXS z*JiYkiDu#JH?7^#MppSj?$|;x$xRZMu=^v2G>tV~AvJn45trvV0J}H3C8JCpc2uOS z)y)x$jkNi*F(9|{C)PU7PFFE=q7#xTn#D~6CxLE^n)<5#Hw~{<$d?J|m+6HSeDr_= zK6>FxJ~isa%0aaI$7M3Mv)Sh2(*7IOv?~|8PC#AoXepqA*cFMccgR=fGK|l$cpM{i zsq*%VPjQmp1wZI8d#c@kx1s1wXniIHIl@%i`=^q(jq`*HBHt||v+*)sy1dDxR$09< zYjO1}&vVGa1L2^4Z6WYYF$*)Tu8Q{!t#3Jl(Z!1l6@OxE)=4aIFaG_s9J8Ik!z+vY zQVjw=25=9PA7W;{nHgJLV060h@QzQ~1686XNVK~a*l(3M7tGr~x18vd2*a(V`7 z5%KU`5ynj-&X%F(52#)>pKaw7h^k^}cJ(`1`A50TVh*P%JWM4G4%q8TPs0A6>fSP} z%C+qpC8jVzVgjOcr_vxL-NHmE5d@Kvk`76wQ$Rwxr4^J8MM)8)5s;8Z1O-7*LJ{_P z^E~g~?{VxO`|r2ckF}hxIq$x&Yn)?@b6nOuTc4a8^^8JU->Okf$-h_R3_QZTX?P(` z3{w&PWrru_*2+w;A58sN1qn<3U;*Fykv?&eUvH90n(5i50fx?JjQZiUA0l|dD>}?dDMvxPl@3F6Ykivl0 z=#c)U#@_%oNnKH!6Z{twqcM863}m|Y;zz!r7B6#WY!_uYyk}I9HsNEo1*c0dT7MKg zL;w)<)hd7iAJ@*|)UrRK2p<@qc#$IjCJG1iLsWiwG=kDp2;qaL&V(%TD;7Lpo+yG! zxK5EABYYT9G~P$BO)we*Nhdg&nFz2`@I*ck-knwc|HF1vdUoG|8Nxc7yXQ zaAo!C-yGnPA&X>}3Ilm+=l$G+{YA)aB3p)$_*MLp?rX1Kr|vQh$|(BYyHtAl)E8rgnP1Fgb7_gadeQpl@!IA@e*j2Fxh7WoI1fapOJJ%75*!aN> zyEmqPeRJ;3j0NJY1J(r?uVLL2jZ%OL4?sD=Cacqh%)Ew?7crbQb$O10<_s8#^J)@G zYn9xF!k?<{00vLzxfZG9Dd_JEL+N)f03HN2T2cVXo&hskNy^sjw99F)2{RE&U+w_ME>on5<2lyQR11HKYE=CYm*ZOa721H}9WQ*DK)9lLS%02KKqs6y= z0V-+{*~S~G52#qwU{__Tq%=u|zETgIODd0U$FpkU){|p@0L|;|bG?93UjqRLw-Ekq zoa%fQ=FEeoM}kowDr3WDSXt@;V%GSjgAxEYszP5KS5=$}dhVpqdtmEd7OJ||tb!dl z2fwWZj&OlLX`oNNH;J>{R)xeDuw6vu*6gi+NLjRS2bmb>%E_5z?BXI9egSLK;>oR% zkPn({O94V%0?JVpscMG%0(vqqe=(kH0xcaTq}sumcxQ+uFK(8?WvI>b<7MA>ekp?M z3Q*=gKI#?N8Ou(hDA*HZ{&K1}gJTQe4|nC_L_Zx~Cx?$b1a%EuRTt7~o*NqxEd|56 z%6_7~$2xRZp8WaLijM3ZPtOFbQL2>|FJgcUuuPvbxnpsOFwWWyUD=sKk@xRO7(yx8 z_89f~QX;dWF z`S@=rIHC(0JO%=S8n?E|9UY4AVqqN~i^b#XS zhG$*E5U@AKpBz@|+aZUd8h4(o3eFa>x(b7DFYVUc(*wBH9IqsdeF9jXmSP)QT+a!Z z(tIf<0fRnNC{Pm&)Y+0o61o4U5wpR8=U}w*l$FDkKWzr2ergxiiH0}uN`h6b zjA}-T1V?l@pIIY6ML~BgL%V7{KQU}XHEX)gURmSX7m(-ow&q=K7bi%2IW*#dwag;g z4P^US;$NU|r^`=nDyZ}xDY{2+0!SGf{wx4R5LSw(erq-}BA!*pxCBePLK$oZZ5aZ( z%d;j1>37?JOwAz6=$GV419B=CuDx)9una)H`J$^-tMo7EPI$M`J+YsluadY;TmG%* zJ-*-I_E_+S2WbruX030~zMrQBycKqcacKzc%jp6MYS5$`_2{yV|MaJ4Dm7z>~`I)PlZV!-C|$`DDC3}3W?AihoLEKqUa7eHda3?e!o30nvr znv&@+>=ty>(D)PgvlZ#|w?8#S5J*|}1IB~vK!SAptWUZ1e#Gbmx=ZrKB$dFPLkm;> zJ&6kig0+MBBCC1#8Hrlsa4bbK^Ne`YhqJ^NU06bM>J@*4$o@6)F=oB=4`(ru_Rn19 zEG^Q%b_9ATg;-}#mV}@pn@I5e1pGK=dASsxGa!;~`{H`PPU)bN|9Lqx>aDBJ)e!1a zpy3!s^Ix3t@-RMV#Q6j|lB!%X>4-HrjNSjGfUcgZ1(mv}Z9F8zu(jb8|KBWAfLdWy_Ji zkK^jk5JDk{-R}C2BrQ@_FPn3UoC6UTUw>hsU&YFr@Bf1#K`Blmf9fI0JxfuplZ7obb#|-9>k6Tm?Dip&jJ$2>QDpbQ7nQz6FD%`c#NPn zf|m8AKVuh6Wou-k#^7(it`b4coC`B3bii3`^mdPJLF@wztB~z921PAnD&%=iHn~}W zM~dHGxH}MwJ`k5Joiyu8XwvYkWbDbq%4rB%o8R0|Nwk826xCACQw+Wx&?N!?XYMqVtmBflX#`itrg=vb`$7 zf)A(y;LVll0%4Y-e_*yj@C`&efc})e|MN6cfriBc8Uto)?ml`Wp$;fIL@^+0}%`seI%Uo9@TS#6M5)gG@zs{4x)4K-NYnJ zgKNl`6ja$}$0?ETW4a2717VO;L>BT7dZr`+u?SG;i>kuq{SKwH&m+;6fYYWywXOtU z$ZRKJ02^;bj8Mm%2v6eu4bnWI?{JzV`oG{JPs*bNzy%mlm+Bzk7AO}^nwI~di+P{r zI7H5cv9qNE;YtQW5R5T-kKqe2N>UCoIc)pRg2MkBV?>fOLpuZNRm95F%UfW7dzkSV zFBM#Wp!PPW1Xht$unJj+>;uh@APKI1JwPFi{W8>G$hyi!mmyAYunVHh-DH!WYty=kdKMPb zVv0z!E%%b+zSPtCf9u8bh{T@idLezbK7tz5Ucd<{KRWr#V~=Js!lXJvO)0J^ZzgULgo=Ec`roKS(faD3&wi?r z&q`0C%0AcG$K)!%o~)-{h=*w2!j@kNEQ)ylgE}beN@VXzViUk!2Mk*M4dwPU=gmFD z+;xzMXh%BdRF`klFM>Ui(A_b$l&5wlf}=X&-S-Agwz2d?_NKB1)kU}zc2pMS#=fsX z#0F`h^7%RR=q#~VuU#Z(Hz@30mxe*Bv^X|z#HZlgCG4e59qVV*RrYudfonov^2gqK z-=mX`P`C^CJwXfQ2h>HAN}RWgu0<;Db4a0+jQ?*yLd&2u0sLY#ct?6E+F*&Dh|A9(XZd9qjxrRJA~_Sk-EDKMq8Bnp zxDSB6k3YEj2y<&68&z;&mtw=|P&mdwSo8#k(3N&R5T(pi!KbmZ4zhS1MialdctLeXWEjY(C?P!qq{&XuX7`g)?~z?!{#G8W@hJ>5%Ey^w-90pn*kioWp30)!}gLI%HB6~x}knXvT#{fQ#a?8~?0DN!( z_^{Yvxl97!W1AhoN2~2K3cxF2L;yb0Wi&{Ez<_)(03TZ!4k~bGER+CzaFOdFekIt2 z?v8fHU&t3Z{Q3T**_HLk#~%5TaCTeZ+b_4vhEF`#+61v`mt%OM^NR;{|4ojy!qKo0 zInM0Ki`uG%;zQqfOitlq@A`T8Sulr?mxI`6SqGFf9m6O7Q@$k#b-XpN633?gl!MS| z|MJ{Q@1FjDf)!MVK4;0F`~Zu`r$fTPIG$&OPtnrPh@J^q_ z!N{K{RAxfo^kO*`HlVSR+-9DQh@VG~0SrVik^2L4BE5~Td*Z(WE*G$zpZM=3oPb&o znU)htBippmbQ7}75VZ)Za5#uJFttN{2Xj^P%)|8OSHN!pmRJ}Dtz3UDsW#d?I~QEV zfpM&^WvpopVDEu4C;iRWpfr5JVQ|p=1+qZlGw5xT)hF~Lg=6q~^`#3h!whLpA zzG8c%lzs;QlztwYDlwAN!%{-w16^QA zC=|v4p8@aW4;)hV(@-c?3(d(o*yr4qyQ1}9dDC(7;y0@6GKxqk*X>0=Rg`@J!h{N8E=aWl=--)n?y-RG-d3q{;M zelDe?wFmOX#kSMAPz3%$&b+7)GyY2_X_-5|FDrQpUCLMcp#_aSEq@{ z2+arcKl{t2x?e$0MWslKwt*KCHCVO_VQLvln|3`?u98eRe|%t;01yccj+rO?Ix9dn z?VN*1AZ8h#1i4CmVwoLyntfkgAi$1biDa1k8YJq{AVE#RuI)q`@QFUHF`(878egHHqmpmQHbd;0g&&r zQez%S-d4&u;ZC<1FxAYMz2F?Dt2auq*Qv5(_T$|N5B6`xar5S|!Hzs>V>cHB*pkwT zLGx<({E7NcckkPj!TD?n+}D}dEcN2MFX7o<{}$U0sD&sM2qY}(0T}oj@mXuzTlIID z`pE=p7{+M&09lTg>qm+R4!?b=)%@mcO8l39=7zQ{KwHNVT5}uS&$@i?{1su~SZ`l9 zdhn8)vXOAPgq2p{`uVZ?kp&9o2fC59G@6gU-`^b2BBgJP`lCI`wDPT=2aiFKK3)bf)wu%N zGvl%l>|$O|S~i0x*kU~7AjfaFHEli4i@f6-v<#T!B=atxG{DHAhX@~L?>A25jcqPQ zDPUO?u$?ueQn($N2u~woJvsnNy+q%}8t;h$I;>y>gkM8%L~XzfWR;o7MRB=Q7l}08 zD%MPaKsKX0p)68YABJi-eh#mhS$%u6h6GYI+{J$rv`UMXwW$wBID&I)G8)TuR@)SK zLW%hRQP>6nO-rm zzyEC)zr&rog8rnYn{UR15>Z;H&3AqIuT;2HzA+eFR~ba52F>IdEyPVMQoq8+Zj^cY z$3(rZ+;{m>s977h^~2vqF)?&+_O|h93Q5odB3TwPpJscZzpL+@?g(7Xe^9_dPZ70_ znmkKihYp!))KJooVNb#tH$_>ZaBI^`^62AMBl&~lZ5F`x8+H*_Y`Vv@jEn{K&oG7^ z#hhshVs8{JOuoX)Qum&~?8~YFAR~laTL7lsE)tlc(|=}PMxp{R8~{<;Be~LGzP2QQ zS-sz9kPbY-3TPy^azQz{4}L8i3INAO#x@RYEG#G_OLM!R7cm^BE5c?)b8FR*wlHL} zXFV8RK{{ajSs;p@cF4F8tSx8+A7BFP+!2S64dABtv0p26;l#rbNP(6UYlieDj3_`N zt8R}89yk!CgrJ92@~7t!Jj@|v*%fv2Y0j5GTi4SmJr*6Z!oU$q$w3r?chQn1GzOOn zkHAd#PPpEOrN|iLzMyl(XyWsV(HF~5)R=QR?gYKMB4yTdT7PR>-Mnq{NNW%0n1?=o z^Z<_Wb?Fx5V0)`?Sra|~t}<-V&S}BQf?`uK2dBbT%kH(Xa4W+N@UwP5TM016>V-){ zSIz^_WYWEY&z;3(P4GeG0wd5xzi5g3@J(M1%9eXF^caL+AjRupc!Jh@c<25?k=V5M z^wHbnKi?ty1jdW^mbK7sgxP@U5b#bfWTy*-9mA}C3pcL|%z!&E5oM-TOWd2-fJ~D; zimJ4JQt%qGUtWcTd+^4H-ywpdY*EFzGj0;jL>&kOC&5`t9fUN=60-+@)7sR_a|ewMreW2A zZ-NvLF-R^T*Tmj)Xoem*02%&}Ff$4TxW(8O#N&urAua;|8VAtM)La2b0AkfO(uXoH z-`n1GU+tfQ*f~H{(DC;WB%BCN*AWfATXj7e6QI<<5<*Z>9+ukaom{t$T87mv@m1>;=C1*Qo9-W1dvjT|I6|2eI6dj&54_+2iuV>=oVJ4!~T+4`Oa8EDb>WulGbEk?9v;n}XY>%NPsG zlJMa)ev|v)zsWKd(`W}Z49e98sGq^c!JYG!%l_5C=8K*1AWRemUKRHkQ8EHv41Rw0 z7+Pd`2~CmaUa|jN-X0QA<_TGZxMjB<{N06nP>Y9Td-9G0qA+a)onYyKG9OaePuH!( zqZDiPaMtUScAnfnAlFoOKFuJ3S z=xJYl63MMp|HorN#Zy}h7M*p3z_G!`YK6Q1H14kfRB1(HDIQj>OO>L-M%9sz7FHKzD{2tX3thWT!ul4&p zbE?vw08z(;qtK`aSMqibOqWxMt{-^#Aqnb;qKzJi$t$IgqtDh7iCJ}%P&s!mJB&O% zv)5LN5r`LBhnX)~_5|e08Wyeqis4&IoT!&XcU_S_go>igL3xg#zx~aE4p*n~>2c{( zdN735M_~DK56H1{^`zvUtBA&CsU&RPH6l8?n<@pP32;?`uFgZ<`Nms(civ|EA%-X~ zy$cVzBT+Wn)s3h3<~|xz?*l~%6bZ3?SWM{0y0P$3l4OW3B+c^!KCS!p((pTQ1KnTg zp@6lgKzU;z4!Yd)?A&HCJ+^WR_Bhm)Z8HM_Z&T;1!0O z?oPVo6PYE9VsbhriWiS5H$E;r2kM1Ez0KyS+M($!2FSD9Wab_?S+e(6i=a@YxNL=alKQ`&TZ z8eTgu3`Vi^+MQlK7|umVa49?^yqYpE!4AH$V0Jx*OJ93wF^Zl3OsS0#H!ZE?)rF|N zYyCu-^Yif&YAF}+;0{=G;8hd3HMRb|%&uryh#bci)Y6WJqcH25>0_CsfuBDz>b}!n z)_{ZRSIal7bljYNhZ~X}CRrol@Wj6heMVwwIPjSg{BKG2DqD~`fy^!)O~?GYQ?k2M z)_Mn*HqWN)JRD${LN=y{BDqajVR`wce%rScUbs4NM$T|{vkAIy;g3oz?eKx>Gww4p zG#Zz60nT-rl=chElq@L9SUB-^#{6$^i2olylK7eraf#mygOLFggSQ^00Mz>O1cM72 z*4T(~t2Lq~I^AMa8z=`$hOdJm>2JYPABB%Z7S$@WBucqJr+Z7*9caMfz_RShPBaE)Jl;T=~oP^t+hS!qJaeTlpiQV^T`R_sqLu zmAY>|GVfPp-o;3^`gt7{VBl{(#(9uyhbF>(m#-XhRmp-i!GEc%YK4;zl|&W1d&N8V za40a=X{s+Zt~GIv3J^o-V`%9QpNH3Dr|w>?Kxp;tNHDCMEdCCfDf zp>j-*|AN9u7~P7nBCMWvusyrj+vt?4`>R?4)xv$>j`UH-nFGG1JfDgzh&i;{Y zRW^{%{eq+1drob^^QqH*Qa<7=MV2hl?owhI5h}U<5BofT_&lEv1QANtLBVd^F!0Mi z)shn4nn;|pn)HN{{(BW^p0sr5bDb!cl-mbYA93AR#M~<6q{hSZUu5XlxZe@^#uuojFjxHNWTS6rM2=Q^zR05e<}G3UB<~@51*+}?*C^HGH>`H| z&qQB&-yye-<-_y5l=Iyt^v^6jRHdAF?UBlSnH!{vwUu2HfqWaugI#P2jib%Z;$`#j zKt8<1h6#}DdfGdO5##o^yOFdXsUS$rcqt9%)3gOseSbOskH5 zczkF(G%ihD!+iy6eVBNqMMFzLH2`bNLu;Un{(^@;G5-Kr?+?h3g)?3kF3Lkg6fA)9 zk%stMAmO~q((U`;>$zry8WS6$(7;ioYTEc%I@FbaM|=ia+QF_O>r$!D`dQjxtc>Nm zcc~DPZ#e*30FbgpK*2yy5OyshI;7Q#qii^rkS;NWe~A16s156gjoWixBT^7w zh&1CEOoDZ4=Iig>OwdFm3UJ8~Pz>|4E`%x^k?-B@yeyzs)C1BK%p2K2r89>n?InrY zBL3yo+lTv+oMSKKM2>eQWfg1U>Kk!8(=--OU2mS7vlbroqYhiGSK2xZG4Fhx$ z!GjtPP?dO*4wz@bFMbZ1;g1^U{18iy#?qyKz#Njx{u+jC_&ffa^-y_c&W0kXSb$Vk zmhXUzA@a3G*K)YG zIa8{g)$s72Ur8`5%rmNH!cl>$K4aAySkBUT6wcm*%QM~a{>hu|j-8qYaoFXIx#?1mZOq5Io>Vir@ zV&Eqqu$Bk^NDN3EnL_98*cD0VqK*u6U%(|flu)*xbSg4S314MxD* z9s`$Yf@Gb&s>?bEjx7Bepdqn@byaXkoPg>l&&?N~sc)JpcIJ5J*mL-r3#Ewf)JFjl z|Jg$u46Xk=K2|i{re5PWRv+cCio3!Us>`%}%o`UOSFl)Nux_BpGB?{@c%7yZb}AzV zy>N9x?Ve4yVk1GZ-MQ?R_4p`(RXP7!UctLb0LSDy(1#<4K6eWlOL)R_0nWH9;ij(m4F&~~&jJ=@k0RvYp&R=7(T;E|2t7Xw?=HL&JG zqZx-T8AbZ=Mbi@qgV2q^AuAxKe|iX7E3J+9tk(q(l3%3WI-l){LO&9G&hT0g+zyt8 z_Vn9oTnz=u3pi(Tij}SxIsjLEykqU&CqLiGfT{J!)z$h`=RBJ^aGu3$!kK5L2XlGp zd915M1)(K&##L;WEu+6lhNz9KxzqHMnLj@{C5q6lgwI2C9eiFwDI%@c(uIN}pTR4M zO$G8}$DYow3YQCc(g8;M#`vJipTltaIix<3T<^k8|D`8){{Tr67w7qN_#v#Y=gB6n zlTA1(BQIBiH%8J5 zRUy5Fa|WtoSS+4oTrq8otU2Ap$HBn1P?$?};m&t{OZ;D5`Ib~k)xkFGLwp^XpGx^s zJLQ^!T~Ssue5o116;3_L@#*=zWE7$shmyV0B^%h{1a?tj$%8tH1I|QZX~vgSZPf|n zngKCY0m1u3&glU@3ARK`V#&Di4ZYlZ@2qS-a^Nx1*8OBImH7}?WpZ0BE7~%##CFr5 z%jg}=6{=)~=m+AlvV*NO=E|6Ug(qJGx-qP*4=u|xh}vYf+vzpj(nD_K`!3LWUUSX< zeUXt9ui{$2CY|gy3H$7>Y*G`Y7&)mK&ItaavzK(VhL7nZFy~E)ZY-XiWaDaI_{rS3 zn)+a(xlY=xkZ*o5#Mo)OM%3x6LPRp7%favC3I{DIs$(wQ3+$+*8jtATq1k7M^eJD2 zC#A=L4YHe`4&81{yTL(tqd|4gFp4OB(6uh{t4LP}#iJ1l$yUD5fRec&V*AX2_c_+} z&<#JoV8@P2sC8AXyL7)jj67pi!2bEgKVc@f>*8aw$+K$r7frct>t9h!UuX?k5SuKq zmvf`!a{2Hy=J!*lM=8}(gJJE#Wl{B{_}N2A`bO(^7$N=g@dXe2-NLI!^0KOhoLw~= zWmR-{7j0IE#$_R65R=L&Sjv>E#YW@!k^+wj*6xinr=g{xa*w<|kR16E_c4{Pr-N&W zF}1XyQ>N5#SPLuqpwH*K%~8mXH*#Y1b%p`sB1z2oGU9!^OJ_nbE;g zy|0d^_ylO)UVc;gFn?_FaSrER(_lX=AAHNq&V6UH+9SXi7u+cW&@#~;5{4X>Dx z-4~UrNOU=JdnKtBq%x6R$9MMu-bd*?t?QW#tu$iRAM!(@(v@nm4Jvo;vg6IH)?1lN?`G<8Hk?w(mDS-)6jd8V`WXA;lT2Ny_-u=0Hf!577(On2g7sZm|_f=w1gjZx8A zw%WBiirx_@r?ZhtFKzZ?n5|wuul+(y(+ky0SJh0*3-8KA`HL$|LAzH$6;{x{TkAh2G=5 z3KE}QPu51?e~=L^oFeyBsv@wnKIUt1&*cGiU!&InG~Ivj{e@x)(^IA(N(akkiqDZp zg6r_^+liXNHzB$c zo$UH-+E}Pd?C)QORPz<(M9B?45?(a55{luQQ1uR2kJObNbokXMoosJez75ukbBbja zvcOMx)@xz7us~QqDwxe51rwiLOn&|BktmaW4`?%_%SC1FPrp*u^{uRCrC*rkl80awiCX^}y4-&w}z6g{5&cK>Ml98ezKHN$P5U&yh8vVm<QEMS^a zhrXfRc+g}}Puz*>+STi$iqfBazk8+$G)SL#Q`V5>Z}y9Ams5)p$*6pN5%fk&ZetZ+ zPOe-ZAJtVX`u_aB%%b6iLmJv`BF}rb)*7l+%X}u%#Ga`SH&v1!W zX;^VErs+3^`)GqzULs?qf1kWca|9+=g$W&Qr6 z!8j+%GQ6!d&$==0htYU0f0ivOE}CKeYZo5(uJIvd4#|@5yBD&f44in5p%^|f@c4`OTMW~>X5LGMv<4+$D>ey#ggaYJo~E#Zwy^zPq1&X*5ruxOR&wt4wi zl*RF`kNmF>FWzD~{H4J1)#`B$qnu2Z*-$#uY`Nh9i%to4PzCivWmxWCejl2m$E?=o z#mzf$RaG(PpLP4=;GB7WRYlPN=m!iM*Cl@H%xHxSLK*p1(<=fblG>J+WHo&MtZ;z4N=yLg6; zH?e2Be7#HjD z1K0ctbeK`g9{)~K?y&@t=jbWZM>SCM0c&38a+dsv-|E(X&xCXq%JHd|v_`0kaDVgY zpB@u5Y_Z!#k$)58Z&!!rem<8UOZB@zMoU9Q4oxa7c1AvsmIAG}%0bMGxnTE(BJ#kk z59IdFW{iMp5BJzYfbVXc#GE7)oxk#tuw4*5BpLd$bt3?>6ev{3R7>Ie~J& zrw}utCW@O34EU!q-Ox|wqRg;Tbnv`7=i*BhgbGCmcd;#e;bPtLkzoGYH?cbY2?UW4 z<_8U1KL3bYe>^6C?f2V3sZHo5ZV0+2Ip@Lf6wtKCf`1MZ7DmYB9wW zK>`mra?%dg4Y`Q~a=e`~15DU0eFg}j)CcZJ#c^&gq)0!7zbx$ZDf7i_kA{*J-v~?p znU=Uxqz;s?s|%3=RXlp<54G}?T0T^%DrMTL98Dxf<<#spArjiR5Bi{YJJX$a)QEDo!0hm%_1F(@N~QX0GWnuAZB;6lc4mL(pN`Wm37xvGrVIkO6rmI)q!rB!0F#FFR9$H zsK3?$d$-2J+rA15HdsxM`n=D#nx^4B-D(jPy!W2pwCh#jL8((>5oV3A3(<+A0M0ZS z<9bELMrivYj#FK4=wK1ImJ>n_7bE1bfp@PFTvibx7+tu2H%wmy8dj5)CFEN9kECj3 zZ^GPFb;t1vc+mN$+Y z!glqLQA^V+Ef(2fGVIV+&*ga~Y`5qfZ1>4G0a}EO=7Jp#7R?!&z;-n#Q2qWmD#N^^ zw?*JZ48|&%gC$AqCMxIZgBK0R_Sw*|t{an<2l%!GR7p}ZU~A~$SOuJ0bB!*{TZetZ z;Mf7Dw~b2SxCZjXD2%e>uqyX;W6d%%mqCX#g|+QRv-t5(d0P8;90L;8tTf|g#gpG3 zE*&dQN{$2L@O5AjUM@`!lCjg_S3!goaZf;F5{361bftyG+OKY%;P{qu@g7?q4B)^& OS5>d6yihU^`M&^Z)`AlN literal 0 HcmV?d00001 diff --git a/packages/client/src/components/layout/ModelSelector.vue b/packages/client/src/components/layout/ModelSelector.vue index 91c5ee3..f130c74 100644 --- a/packages/client/src/components/layout/ModelSelector.vue +++ b/packages/client/src/components/layout/ModelSelector.vue @@ -1,6 +1,6 @@ @@ -89,6 +126,7 @@ function openModal() { @click="handleSelect(model, group.provider)" > {{ model }} + {{ t('models.customBadge') }} @@ -98,6 +136,26 @@ function openModal() {
{{ searchQuery ? 'No results' : 'No models' }}
+
+
+ + +
+
+ {{ t('models.customModelHint') }} +
+
@@ -243,10 +301,48 @@ function openModal() { color: $accent-primary; } +.model-badge-custom { + flex-shrink: 0; + font-size: 9px; + font-weight: 600; + color: #fff; + background: $accent-primary; + padding: 1px 5px; + border-radius: 3px; + margin-right: 4px; + letter-spacing: 0.03em; +} + .model-empty { padding: 24px 0; text-align: center; font-size: 13px; color: $text-muted; } + +.model-custom { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid $border-color; +} + +.model-custom-row { + display: flex; + gap: 8px; +} + +.model-custom-provider { + width: 160px; + flex-shrink: 0; +} + +.model-custom-input { + flex: 1; +} + +.model-custom-hint { + margin-top: 6px; + font-size: 11px; + color: $text-muted; +} diff --git a/packages/client/src/i18n/locales/de.ts b/packages/client/src/i18n/locales/de.ts index a011e98..8abc87d 100644 --- a/packages/client/src/i18n/locales/de.ts +++ b/packages/client/src/i18n/locales/de.ts @@ -252,6 +252,9 @@ export default { nousApproved: 'Login erfolgreich', nousDenied: 'Autorisierung wurde abgelehnt', nousExpired: 'Autorisierung abgelaufen', + customBadge: 'BENUTZERDEF.', + customModelPlaceholder: 'Benutzerdefinierter Modellname', + customModelHint: 'Enter zum Laden', noProviders: 'Keine Anbieter gefunden. Fugen Sie einen benutzerdefinierten Anbieter hinzu, um zu beginnen.', builtIn: 'Integriert', customType: 'Benutzerdefiniert', diff --git a/packages/client/src/i18n/locales/en.ts b/packages/client/src/i18n/locales/en.ts index 72702ba..7e1f190 100644 --- a/packages/client/src/i18n/locales/en.ts +++ b/packages/client/src/i18n/locales/en.ts @@ -277,6 +277,9 @@ export default { nousApproved: 'Login successful', nousDenied: 'Authorization was denied. Please try again.', nousExpired: 'Authorization expired. Please try again.', + customBadge: 'CUSTOM', + customModelPlaceholder: 'Custom model name', + customModelHint: 'Enter to load', noProviders: 'No providers found. Add a custom provider to get started.', builtIn: 'Built-in', customType: 'Custom', diff --git a/packages/client/src/i18n/locales/es.ts b/packages/client/src/i18n/locales/es.ts index ab255d0..66b9442 100644 --- a/packages/client/src/i18n/locales/es.ts +++ b/packages/client/src/i18n/locales/es.ts @@ -252,6 +252,9 @@ export default { nousApproved: 'Inicio de sesión exitoso', nousDenied: 'Autorización denegada', nousExpired: 'Autorización expirada', + customBadge: 'PERSONALIZADO', + customModelPlaceholder: 'Nombre del modelo personalizado', + customModelHint: 'Enter para cargar', noProviders: 'No se encontraron proveedores. Anade un proveedor personalizado para comenzar.', builtIn: 'Integrado', customType: 'Personalizado', diff --git a/packages/client/src/i18n/locales/fr.ts b/packages/client/src/i18n/locales/fr.ts index 56dcc0e..ca1dd66 100644 --- a/packages/client/src/i18n/locales/fr.ts +++ b/packages/client/src/i18n/locales/fr.ts @@ -252,6 +252,9 @@ export default { nousApproved: 'Connexion réussie', nousDenied: 'Autorisation refusée', nousExpired: 'Autorisation expirée', + customBadge: 'PERSONNALISÉ', + customModelPlaceholder: 'Nom du modèle personnalisé', + customModelHint: 'Entrée pour charger', noProviders: 'Aucun fournisseur trouve. Ajoutez un fournisseur personnalise pour commencer.', builtIn: 'Integre', customType: 'Personnalise', diff --git a/packages/client/src/i18n/locales/ja.ts b/packages/client/src/i18n/locales/ja.ts index 6965b91..cb6b002 100644 --- a/packages/client/src/i18n/locales/ja.ts +++ b/packages/client/src/i18n/locales/ja.ts @@ -252,6 +252,9 @@ export default { nousApproved: 'ログイン成功', nousDenied: '認証が拒否されました', nousExpired: '認証の有効期限が切れました', + customBadge: 'カスタム', + customModelPlaceholder: 'カスタムモデル名', + customModelHint: 'Enterで読み込み', noProviders: 'プロバイダーがありません。カスタムプロバイダーを追加して始めましょう。', builtIn: '組み込み', customType: 'カスタム', diff --git a/packages/client/src/i18n/locales/ko.ts b/packages/client/src/i18n/locales/ko.ts index d3d2141..6a84bbb 100644 --- a/packages/client/src/i18n/locales/ko.ts +++ b/packages/client/src/i18n/locales/ko.ts @@ -252,6 +252,9 @@ export default { nousApproved: '로그인 성공', nousDenied: '인증이 거부되었습니다', nousExpired: '인증이 만료되었습니다', + customBadge: '커스텀', + customModelPlaceholder: '사용자 지정 모델 이름', + customModelHint: 'Enter로 불러오기', noProviders: 'Provider가 없습니다. 사용자 지정 Provider를 추가하여 시작하세요.', builtIn: '내장', customType: '사용자 지정', diff --git a/packages/client/src/i18n/locales/pt.ts b/packages/client/src/i18n/locales/pt.ts index faa3905..ec1b382 100644 --- a/packages/client/src/i18n/locales/pt.ts +++ b/packages/client/src/i18n/locales/pt.ts @@ -252,6 +252,9 @@ export default { nousApproved: 'Login bem-sucedido', nousDenied: 'Autorização negada', nousExpired: 'Autorização expirada', + customBadge: 'PERSONALIZADO', + customModelPlaceholder: 'Nome do modelo personalizado', + customModelHint: 'Enter para carregar', noProviders: 'Nenhum provedor encontrado. Adicione um provedor personalizado para comecar.', builtIn: 'Integrado', customType: 'Personalizado', diff --git a/packages/client/src/i18n/locales/zh.ts b/packages/client/src/i18n/locales/zh.ts index 9dcadc5..9b1ae51 100644 --- a/packages/client/src/i18n/locales/zh.ts +++ b/packages/client/src/i18n/locales/zh.ts @@ -277,6 +277,9 @@ export default { nousApproved: '登录成功', nousDenied: '授权被拒绝,请重试。', nousExpired: '授权已过期,请重试。', + customBadge: '自定义', + customModelPlaceholder: '自定义模型名称', + customModelHint: '按回车加载', noProviders: '暂无 Provider,添加一个开始吧。', builtIn: '内置', customType: '自定义', diff --git a/packages/client/src/stores/hermes/app.ts b/packages/client/src/stores/hermes/app.ts index 70d85f6..5df84a2 100644 --- a/packages/client/src/stores/hermes/app.ts +++ b/packages/client/src/stores/hermes/app.ts @@ -19,6 +19,7 @@ export const useAppStore = defineStore('app', () => { const modelGroups = ref([]) const selectedModel = ref('') const selectedProvider = ref('') + const customModels = ref>({}) const healthPollTimer = ref>() const nodeVersion = ref('') @@ -73,6 +74,13 @@ export const useAppStore = defineStore('app', () => { await updateDefaultModel({ default: modelId, provider }) selectedModel.value = modelId selectedProvider.value = provider || '' + // Track as custom if not already in the server-fetched list + if (provider && !modelGroups.value.find(g => g.provider === provider)?.models.includes(modelId)) { + if (!customModels.value[provider]) customModels.value[provider] = [] + if (!customModels.value[provider].includes(modelId)) { + customModels.value[provider] = [...customModels.value[provider], modelId] + } + } } catch (err: any) { console.error('Failed to switch model:', err) } @@ -122,6 +130,7 @@ export const useAppStore = defineStore('app', () => { updating, doUpdate, modelGroups, + customModels, selectedModel, selectedProvider, streamEnabled, diff --git a/packages/server/src/controllers/hermes/models.ts b/packages/server/src/controllers/hermes/models.ts index a83d7a0..164c179 100644 --- a/packages/server/src/controllers/hermes/models.ts +++ b/packages/server/src/controllers/hermes/models.ts @@ -83,7 +83,7 @@ export async function getAvailable(ctx: any) { const builtinPreset = PROVIDER_PRESETS.find(p => p.value === bareKey) let models = builtinPreset?.models?.length ? [...builtinPreset.models] : [cp.model] if (cp.api_key) { - try { const fetched = await fetchProviderModels(baseUrl, cp.api_key); if (fetched.length > 0) models = fetched } catch { } + try { const fetched = await fetchProviderModels(baseUrl, cp.api_key); if (fetched.length > 0) models = [...new Set([cp.model, ...fetched])] } catch { } } const label = builtinPreset?.label || cp.name const presetBaseUrl = builtinPreset?.base_url || ''