From 3c276348b2004aa17227befc28dba5ad59b87d07 Mon Sep 17 00:00:00 2001 From: marc-romu <49920661+marc-romu@users.noreply.github.com> Date: Sun, 30 Mar 2025 21:30:46 +0200 Subject: [PATCH 1/8] refactor(aiproviders): update logos for mistralai and openai --- design/icons/export/mistralai_icon-old.png | Bin 0 -> 4535 bytes design/icons/export/mistralai_icon.png | Bin 4535 -> 3217 bytes design/icons/export/openai_icon-old.png | Bin 0 -> 5019 bytes design/icons/export/openai_icon.png | Bin 5019 -> 2905 bytes .../Resources/mistralai_icon.png | Bin 4535 -> 3217 bytes .../Resources/openai_icon.png | Bin 5019 -> 2905 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 design/icons/export/mistralai_icon-old.png create mode 100644 design/icons/export/openai_icon-old.png diff --git a/design/icons/export/mistralai_icon-old.png b/design/icons/export/mistralai_icon-old.png new file mode 100644 index 0000000000000000000000000000000000000000..a48b4327bbc8ac965aa3b32544f6a8bac0e8b7f5 GIT binary patch literal 4535 zcmeHKdr(tX8V`biASx6Ebi^2OsKU+r4dGD&1lB-=6mZp9?jXx#vO!##d6#*>u&mV@jag!cUE+spBH?FNISTMetJ9RkH;P)Ma^rw6nO17UwVA) zUhDsHc*gqHzxEs`KjGaK6J4vDf5|OXaOIn?Hu1_5%l@+YR`d^Bt6#~>yo+F?ruV5o z5I^30*EjJU!R4Oyi;sx?D%|(SR+6y!=!TBAA$iyBPWF+4`wednKQMO>_}<&7=~;IQ zVFfi8(*bubG)2~Y%I$sos|x{-$kfn5pX)>GmYrAD53^VD{6C->_Qc`iy`-}t&m^}EkwtG9)8cdxZV^2dj>@~pX6 zubg~+f!mgtuV;tG2do+Kh9+nE2PoVpB`7~RX)Y<^F!311YEHMA*1sJ~6zZfn|H!^1Xa$jRJ{Fv->9 zp<7_~hpqcQP04Y~4PRaoqW*JB<1Vjob#|XdP}MY2yM4>G;n1@ytN%1Np<=ea@ip7i z!7UHpIBI}5_iDXP6%Pg6d>-gpm|tS(KiW}gK2b31aYIR1>w^!a#n)4Ne=tLDy3IAY z2K1S=w_LgQ;+|@5(C=0}A?B3MdiYktK#l7*OUdUFyQP(?i(&=sx$?-zYW3U|uwF78U33CptK60#k7^C0f4L8i!{SnnD|%UYMds3v*Bf#)*#digYLeff=V^w!>_) zP)di2XU5Fqu$>S(N5#p~>ez9l4QGqFVlE$wcM$nPPLvlr(uNt7Y3hV=3eZw< zvT53?(Nu+ju+VKbaC zLgvvb4hQtJC*m_(wc1H~3pK6+(1YiItvmsj&oi5OQ#~j;J|B>b2lTTZlpd^6UK&o3 zc{UV}&&MtFim4PBI_YoCvzeUfU?>ka;bs7(z^H<0LnbC^b(0>91V+Mab$S7@ry*&= z@ItI8f&ogaRjNrej|neHt>Q4_D=`u!Fs1WRYJgF`5Eei% zE=M4-5S2i30WO13%pjJ*LYYW`qEo1nEEEk}P@JIx}hRAWq0ML*a$0U$K zCQv{I1U4WTUxtfe{uGK-8v&{kHcgF+p~3)_P$5G3Ff42n0A=7d3e+OQD&TWPV?E5oC_y?vT9~O+K;ZNM z*(l>|I82i^JxQ8W93})d<2l)_1q%v;X;=-@I3VQUs1y0l0no7rW$68}e6X9G;Y_@Y*C}DIg1q#l`F=w2BYpe^+ zhAl=M>>uL=J7Fh&rWjCIj?3g?10=xYGDwVw#E?RQ$RQLL3HT@i%W$c1Iy*%gXgh4f zV~s#ZpcN=kr&jD&$ERx5bi6$qXXXJ&2Jr>Z6v;Te@nm_-jxmvKB=5iUh;#y`Y%#!Z z%m%hDuoLnox59C~m`&#w{EW}TFE|3Ao*Cq&^qrAwMy{7q;HAJb)iop6ODXVD;F;?B zzscqG;xL6`HNMtVQScaTf<0Eoc+SCQ_0t_>vl#LmxlXC zczf4;bf~7iVH3VQBJHCy=T4tHtts;plwUy3VTNP57u(MuLrZ1l3%#3DioSc$p8DNq z0neM%A$f;FgDxf~94&PCKip(|RMecixfX%|U~+ z_dhas^feCmpWXi6(BPca*)5x?nBbGo-umXrk88-smwo)6eSGZOzU6yP|GKg@nS1a~ zdgv(bes5mSqw@5=;JS>(c_x x?>;olU(vZz=QGe3ednKZ>s);Ip6X~xZBDO9uDzhz0Oo*|6rZC0;I&O}{|6W4h#>#~ literal 0 HcmV?d00001 diff --git a/design/icons/export/mistralai_icon.png b/design/icons/export/mistralai_icon.png index a48b4327bbc8ac965aa3b32544f6a8bac0e8b7f5..19d0fa448cb60a1d330baa336ce1aae9766d1186 100644 GIT binary patch literal 3217 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 diff --git a/design/icons/export/openai_icon-old.png b/design/icons/export/openai_icon-old.png new file mode 100644 index 0000000000000000000000000000000000000000..bdf9c6d9793f8644827c48d08aeaf3b9b67549b9 GIT binary patch literal 5019 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 Dl8<-M literal 5019 zcmeHKc~leU77uVFNI^xwh(d`0aly$(5|Wsx5RgO+#t;QunM@`jNEVZU1keWsK@f!} zE}$ZcxS&{~hJ)aMN7>spak|Yulg9f}-r;#h;U_6qjh4HXa zDaT-pw>k?J=HHp_5IhhSygk)>U9FB>c;t8dkBuX$c6StC54rGa+jO4WiHo=rn-}w2 zQu^KRW5>ESPCA@OWQFXPi0^O6toQn5k$8SNXXS&^UfJjfY>ju>W8)uda#LES&6>6_ zIP`*D;>?+++jH~7)}}bIr{s4ymEKrTqO%|A>4DfcK~WUknd6}?FH5`fw!Q9M7nKEc zhCcd(>$b1ibuQSN;k{4RTVu5&&-QT4n0ec2SVDv#zhLs7Vm!Y40dl}PuMVMCd%OsG z9D5@<*?3^vYj*-E;Ce*A=}Z2-@Tigjs=n{k<;H|9yy>+CPZ|{)yP&V#%FCvCHN9EZ z1WhZ?SUvGzVR&-tirmbQNuV!IR)0*r*97HE)RhK0EK467e}opZGQQLn=iL33qvLON z-)tn-W!xt0kRQC{6Z{=>DkB;f`KB+!R5AJV3Az62osqm9__?c`*A&(q^4v(tvtIbZ zcX3ZnqU}WWeBG&9@|cpTCJ>SrU$E?%XrJOu|K=c`&H2hD!bAvT7zN{?oVW4S& z)jGRy-=axYRwA#3S6nvznl*-x-D}_6zT?+9sgkobf-r(xTv2<|>)IGc7%sH=z4Gdu zz}4p+;xVg%9TPL@@taw&zxV@H??9md-`<~AW`I50_>q1iO6qDwSO)MgQ7#usQB4@%; z`RI{5o3q2@n7T<$S+|2y9j|SPLgr;pIs3zmadn49nOoR9;&;~0A$QiYpIjpL1R(iu zSKdiGFh%{Jl|N);cky)h&Q_P2+8aenYjeYy=@UjA8r@$N@sgeL`}tq)YdrRt&in)* zU)&9}-1>QeHYuE|1J)@vFN+Tq+2PttFc_;0C66Z(@ObaKAMN>~wVS#88j(xJf}0x% zu2^4})eWNEN3%9C=eg!YmsGeei6{;=5hr9N1eyr+ld>Y>8W3z_P>weQQ$Cz%E-~5@??YurxAkJ#cu6Otj-|N*37u;jw zJjNO24GsH$%jVTePo_T}C7;ZjTVRA2&$s@HwJGfPu5L9ELgoxGw5#u$9&y`rSi9LB z`IA`rd$xM8{F2a6TK-sED9UBZY}Zl@=XW7z}5&Q42z=U<5CP6-qUi&|P(b zfLF@6gb2EjEY$Mg7$rYR2ZtwxNuZ=v5KBgwJP*hy=&MD%4buu1fi6$q<21^xnfvfkLU$TD(xQKe9xW@(*Nv z6q{LNNoRN0}m61Ee4b z0W_Ea0Zckb0a#>;A1IenSrF_$3`(HZBcK|B%}^*fQHkRCLo5o}p9TVSe+C1fF+myt z(pVrMlhHvsgGu%yODV%37U+~{SAucFqcTIuP$(H0q)}utKR{0QH%B7}0T$hl2~Zex zray%Yk{J+Wfs#S&xf-1cM9ZmEfeM(URVyqCGvVw2k$_8}63HJVqBszdqYmgEP^x7b zgZ_g`qEx}*2x#V$;_v70Pp4Cu3>Jk#C(}O|ErxY^v=_~&6f%*@v}nwQVWa7w)Pm+t zMFA{wG#fTg2ZM-4C(&r)xCC=hc(bRa!SS5IvhbC9)FRQ`^Fv)94zC`38{7nON{b4Q zw=^yrga#MUg9)(AvJmPwsDffZwE{+W_)tr|lPmwGTp;Y{FQcNxmip0Y0F6eI0Zf)3 z9ROu=7QTZu5J3F~W$180XBFxFuo~jaOyNKH8LGoSxB?3O zd67@z_cL9e>G~uFKFRoVcYUVolNk6U2EIRLC97b z8WMyVFn{+}AKHRSMr!#{dJN_ZXY*x+*;zCm6=D&AFc{lmHQtpt;q0v`?dWkgUJw)@ zVg9)3CWY@3?Sk#ubI55~WnmaAsG_B8*B!@EQNUb?S-HH;C9|BEJ1QVR)Y7JM9Cf+~ z7m($c#v99D>ZndzSJs9A1L>sdOIXe8^WD`0uUg*@ct)sPghhMPHHA9i#7my%E-iO> z5S)7YMxFJM>U>R`O)JiDidl=hdihMx7d*Hx-2L&P30)u-Ym`Wuzx6Ryt3(I=ZlN8=D~?BAR$uITNbpNqas9sHU-5_2xSU*#qOb?wxqIJ| zkxx503HFa{m-H4GGrP0D9wGa3#ex6KYI^?D_r@F7jhJVzH&?B1DR1-e*w(_#^;-BTaP~KTb!R@Q~6c!8vB_!TqL}{yX*dpCI<-Es>=NSuMW zs=c2dbI|MH?yU0d0iNK&8H%ZWccy#a&fTcUsr_kZDDU4-iz*GN`0kO)bUnX9gK?QDvSi!!gQGE&x+J+6c@xKp5IH~HW0#F{$bZ7?!fKl67U0vS`qopROt*I_AeO+E&Qs%NW0H_xPLqD^`T`uME z-3P2DYJ+R#scuh!um};>1~<=zzXEpQaD+;8L=>Y52Paz=%DCxSA0vtoHI;LLRz<5rNXMfXu;>80LseB!N5VyeAfJ77p|K%lno)4Fqi8Bu45u@Dh>+7>!F(3YeQ){ zQ43EB+68Xe9**t_i3AyBM$9wO;4z@> zvQZlcXyXQDhM(>;f+PUkaDi&VpqLpfZ)qz-!C+D1C=v|jo+*NXSQ?OiLF*i?%2!}( z{1hWW+t3Jdh$_ODAq}N8q2`q`TC}0HP~aP9hG$i=1)=F`5*8+nAvhj={p=dw@Ik_|SZtqnNQu0q;zm2ttDWR!Gw{kT*)Zg*EJ7eWuf))RpR0z_1B;IKF3fb%Y`39^=n|y=a`mp~~6pH~MWi`Oh#iF|CN&zp1@49j#2gI_WVH z9lUe0Jst^I1CKIP(n}ih!j2x)YWi#=1a<(3khFW1dp zko1LhT5(@62^vD}$y12M5Sev>lp}^jKW0FT)N9bKJoTCE))2f(LmSOv*6RyYf;S~& zc$pFa+(}jjw+ldG{a(C^I1T7b&7TIKW`WzXFbRonfdQbA^Yl`g>Z#M;IEW1_S>O0; z8j!Sh^cpIs&o$hHtI^Mdo?(2$oubNP)Sx&B`CLZFo5Pkd39tAjt-w4#fv|kK#>$9`}mPIL7A6_FKe`c=*key}Bb4}Y9X1rppTn@8lawhR|4*X`&5X2?B<$xlMuk$Z4!7V`GBj9Pk5u|a21_HS5ml;Z1q z+SnXleJR7|MeMRZ8KRw;Tk?kXOsbo4vuuhjH5qbmb4eCbYUtZ5#`W|wv@1#%|*KqMY(lpr;hLsnZ}D+&8SM)_;PaJw zf;Lq5Dt06~CO8u29(B^Nw3JV*T_4K2(jyV#lJn&DX8Q}N5YqoYk?lsHIiUyi%pm*tvm zS2t8Mau^%&Rx_*lPPBYBcmg!fn@7V2#?qGMi`8&=a`1@~o z-w1r{?bG4w=2^3pKPmgYsHy1tqNx*~(;F|srX2F!T|60)>YIu*#1_0PaDN>@PM+mm zt?vE&YdbIVYvyRl5cc<<4yERx_Xj$cGx^%BzAI5wx7K#erXF=n6ACg58aMItQr5oK z=GF(bXZ1d1iDma+!M2SwSxnTDpV%rYD?QlnTBK8MQKo$z@cMb(yE@@4aT{Fgx=>r1Z#1(swNH7Uexh@w6M&>c-ky?!#zMc+OPyBcR6EVU zY%cIP(W3#m!MEpdb<{~BqlfbcjKF!1Z~sb%82W5KTU=x}Y#bi!@oo>j*ii|Kn$U8K zA_+M2q$Br&I4)k2DZc#Ipu*7mk@v1dKXJzVz^OKFJ-#UB%Z(A2%E!J=L>D)Vo5OM@ zDU?)9dg}lDpXPch!khgZ!^(@9rokqq7t>y}^KKbevt}}kQoJ2XXGe3tAmvCgr4a%!XcW%C#BbX%bDEzq* zq`vq%KjS$P;{=adTZfh$!Ps`MPi z#S@ulaC6une2LG0?9Pbnd2Nnxx<#OS@+vT;`Yc>%sX}po3vLU45~>z;@AuVl$K$_y zB23S?0&DSQ`lPZ?gCu|R^$Fp6&97V{(S}IB%hFbu>ihoAYrR3_hvbhbNBUD{7~>5W z+~`IeCam}ETXpegrn1RJN$q6!(ye=6960Tg?;BHGQu{1SLM|bbZHS)4!qR_bw@GtT zrn)wLf!jFj@bK83svj#$sHOZRcjB&j{f9fXE~iW6hKO`+bz2@xrCvTse+R>yexp;fHjee-F|Hy#F$j6>V zDSFOkhI$NVu4MRq_PlqArBQYE+xhW#flsFO7v6MzAN}4ro*q)Q4O>Ft#CsL%eTb5I z*c_!dwUmXJJOaNpzgArpHJBnj?cmcY7jK%KL%5){zBltMak}7sK{i46meTL^QQR*2 zFx}P4z^c|sy*v9B(t8zMWwYD9IW`|}wN~S~CA@1tB3gB~M5$CsE7e*>4TUgK&hEz5jW?qYw=kVLDu|M~8$ApICu(W6!Y z@%|qU>xPg1p4w;q8&Yvx{-6On@z1uww)?v|D}{yOdG>J<_1E*sUabzTK9SoZvGD`} z0X}Bl+Ku~NiIW8%3)s7V1e;KCOJ48i$B(8C?sl6U4z4lnMR(Vi;)*sOQ{ESSP;3=x z6{zXLDXqUdEa@5vqL3)n)E07An7*gug%7Xa|N0>EEe02I>zKze3X8{9luwCkgdG*6g%QVW1O1gKvD z^$VaD18NbV#?nz^=%_Cs$Nu!Q0E`1L0Hx`rr=<=8>M)>=0BQ}O)&puK*ee5*1TdBb z1`|O~JfOxvsZo%FNXUL9gc^A=L#feFYB-b{OivA=r~1<$3a*5O(R!uA6REMUen_M_?h*f{4)=P5B6duFpS(0`5;! z2jMn?-I+BbmCV=)yHE&z%-Zpc%SDkinXO4Hi&)#fJ+I`k*owqQg_h^jXx#vO!##d6#*>u&mV@jag!cUE+spBH?FNISTMetJ9RkH;P)Ma^rw6nO17UwVA) zUhDsHc*gqHzxEs`KjGaK6J4vDf5|OXaOIn?Hu1_5%l@+YR`d^Bt6#~>yo+F?ruV5o z5I^30*EjJU!R4Oyi;sx?D%|(SR+6y!=!TBAA$iyBPWF+4`wednKQMO>_}<&7=~;IQ zVFfi8(*bubG)2~Y%I$sos|x{-$kfn5pX)>GmYrAD53^VD{6C->_Qc`iy`-}t&m^}EkwtG9)8cdxZV^2dj>@~pX6 zubg~+f!mgtuV;tG2do+Kh9+nE2PoVpB`7~RX)Y<^F!311YEHMA*1sJ~6zZfn|H!^1Xa$jRJ{Fv->9 zp<7_~hpqcQP04Y~4PRaoqW*JB<1Vjob#|XdP}MY2yM4>G;n1@ytN%1Np<=ea@ip7i z!7UHpIBI}5_iDXP6%Pg6d>-gpm|tS(KiW}gK2b31aYIR1>w^!a#n)4Ne=tLDy3IAY z2K1S=w_LgQ;+|@5(C=0}A?B3MdiYktK#l7*OUdUFyQP(?i(&=sx$?-zYW3U|uwF78U33CptK60#k7^C0f4L8i!{SnnD|%UYMds3v*Bf#)*#digYLeff=V^w!>_) zP)di2XU5Fqu$>S(N5#p~>ez9l4QGqFVlE$wcM$nPPLvlr(uNt7Y3hV=3eZw< zvT53?(Nu+ju+VKbaC zLgvvb4hQtJC*m_(wc1H~3pK6+(1YiItvmsj&oi5OQ#~j;J|B>b2lTTZlpd^6UK&o3 zc{UV}&&MtFim4PBI_YoCvzeUfU?>ka;bs7(z^H<0LnbC^b(0>91V+Mab$S7@ry*&= z@ItI8f&ogaRjNrej|neHt>Q4_D=`u!Fs1WRYJgF`5Eei% zE=M4-5S2i30WO13%pjJ*LYYW`qEo1nEEEk}P@JIx}hRAWq0ML*a$0U$K zCQv{I1U4WTUxtfe{uGK-8v&{kHcgF+p~3)_P$5G3Ff42n0A=7d3e+OQD&TWPV?E5oC_y?vT9~O+K;ZNM z*(l>|I82i^JxQ8W93})d<2l)_1q%v;X;=-@I3VQUs1y0l0no7rW$68}e6X9G;Y_@Y*C}DIg1q#l`F=w2BYpe^+ zhAl=M>>uL=J7Fh&rWjCIj?3g?10=xYGDwVw#E?RQ$RQLL3HT@i%W$c1Iy*%gXgh4f zV~s#ZpcN=kr&jD&$ERx5bi6$qXXXJ&2Jr>Z6v;Te@nm_-jxmvKB=5iUh;#y`Y%#!Z z%m%hDuoLnox59C~m`&#w{EW}TFE|3Ao*Cq&^qrAwMy{7q;HAJb)iop6ODXVD;F;?B zzscqG;xL6`HNMtVQScaTf<0Eoc+SCQ_0t_>vl#LmxlXC zczf4;bf~7iVH3VQBJHCy=T4tHtts;plwUy3VTNP57u(MuLrZ1l3%#3DioSc$p8DNq z0neM%A$f;FgDxf~94&PCKip(|RMecixfX%|U~+ z_dhas^feCmpWXi6(BPca*)5x?nBbGo-umXrk88-smwo)6eSGZOzU6yP|GKg@nS1a~ zdgv(bes5mSqw@5=;JS>(c_x x?>;olU(vZz=QGe3ednKZ>s);Ip6X~xZBDO9uDzhz0Oo*|6rZC0;I&O}{|6W4h#>#~ diff --git a/src/SmartHopper.Config/Resources/openai_icon.png b/src/SmartHopper.Config/Resources/openai_icon.png index bdf9c6d9793f8644827c48d08aeaf3b9b67549b9..f0d9ad572bb5056aab064b220070544bab105ee5 100644 GIT binary patch literal 2905 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 Dl8<-M literal 5019 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 Date: Sun, 30 Mar 2025 21:52:11 +0200 Subject: [PATCH 2/8] feat(settings): update settings menu to use eto.forms and eto.drawing --- src/SmartHopper.Menu/Dialogs/AboutDialog.cs | 6 +- .../Items/SettingsMenuItem.cs | 499 +++++++++--------- 2 files changed, 264 insertions(+), 241 deletions(-) diff --git a/src/SmartHopper.Menu/Dialogs/AboutDialog.cs b/src/SmartHopper.Menu/Dialogs/AboutDialog.cs index 7f7142b1..04b4e8e5 100644 --- a/src/SmartHopper.Menu/Dialogs/AboutDialog.cs +++ b/src/SmartHopper.Menu/Dialogs/AboutDialog.cs @@ -52,7 +52,7 @@ public AboutDialog(string version) ); } - private Control CreateLogoPanel() + private static Control CreateLogoPanel() { // Create an ImageView with the SmartHopper logo from the resources var imageView = new ImageView(); @@ -199,7 +199,7 @@ private Control CreateContentPanel(string version) }; } - private LinkButton CreateLinkButton(string text, string url) + private static LinkButton CreateLinkButton(string text, string url) { var link = new LinkButton { @@ -212,7 +212,7 @@ private LinkButton CreateLinkButton(string text, string url) return link; } - private void OpenUrl(string url) + private static void OpenUrl(string url) { try { diff --git a/src/SmartHopper.Menu/Items/SettingsMenuItem.cs b/src/SmartHopper.Menu/Items/SettingsMenuItem.cs index cf26d0c2..a1ca6e88 100644 --- a/src/SmartHopper.Menu/Items/SettingsMenuItem.cs +++ b/src/SmartHopper.Menu/Items/SettingsMenuItem.cs @@ -12,298 +12,321 @@ using SmartHopper.Config.Models; using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; -using System.Windows.Forms; +using Rhino; namespace SmartHopper.Menu.Items { internal static class SettingsMenuItem { - private static readonly Dictionary> ControlFactories = new Dictionary> + private static readonly Dictionary> ControlFactories = new Dictionary> { - [typeof(string)] = descriptor => new TextBox + [typeof(string)] = descriptor => new Eto.Forms.TextBox { - UseSystemPasswordChar = descriptor.IsSecret, - Dock = DockStyle.Fill + // Use ReadOnly for passwords + ReadOnly = descriptor.IsSecret }, - [typeof(int)] = descriptor => new NumericUpDown + [typeof(int)] = descriptor => new Eto.Forms.NumericStepper { - Minimum = 1, - Maximum = 4096, - Value = Convert.ToInt32(descriptor.DefaultValue), - Dock = DockStyle.Fill + MinValue = 1, + MaxValue = 4096, + Value = Convert.ToInt32(descriptor.DefaultValue) } }; - public static ToolStripMenuItem Create() + public static System.Windows.Forms.ToolStripMenuItem Create() { - var item = new ToolStripMenuItem("Settings"); + var item = new System.Windows.Forms.ToolStripMenuItem("Settings"); item.Click += (sender, e) => ShowSettingsDialog(); return item; } private static void ShowSettingsDialog() { - using (var form = new Form()) + // Use RhinoApp.InvokeOnUiThread to ensure UI operations run on Rhino's main UI thread + RhinoApp.InvokeOnUiThread(new Action(() => { - form.Text = "SmartHopper Settings"; - form.Size = new Size(500, 400); - form.StartPosition = FormStartPosition.CenterScreen; - form.AutoScroll = true; + using (var dialog = new Eto.Forms.Dialog()) + { + dialog.Title = "SmartHopper Settings"; + dialog.Size = new Eto.Drawing.Size(500, 400); + dialog.Padding = new Eto.Drawing.Padding(10); - var providers = SmartHopperSettings.DiscoverProviders().ToArray(); - var settings = SmartHopperSettings.Load(); + var providers = SmartHopperSettings.DiscoverProviders().ToArray(); + var settings = SmartHopperSettings.Load(); - System.Diagnostics.Debug.WriteLine($"Number of providers: {providers.Length}"); + // Create the main layout + var layout = new Eto.Forms.TableLayout + { + Spacing = new Eto.Drawing.Size(5, 5), + Padding = new Eto.Drawing.Padding(10) + }; - var outerPanel = new Panel - { - Dock = DockStyle.Fill, - AutoScroll = true - }; - form.Controls.Add(outerPanel); + var scrollable = new Eto.Forms.Scrollable + { + Content = layout + }; - var panel = new TableLayoutPanel - { - Dock = DockStyle.Top, - AutoSize = true, - Padding = new Padding(10) - }; - outerPanel.Controls.Add(panel); - - // Calculate total rows needed - int totalRows = providers.Sum(p => p.GetSettingDescriptors().Count() * 2 + 1); // *2 for description rows, +1 for provider header - totalRows += 4; // Add 4 rows: 1 for general header, 3 for default provider selection and debounce time (control + description) - panel.RowCount = totalRows; - panel.ColumnCount = 2; - - // Set column widths - panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 40F)); // First column takes 40% of the width - panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 60F)); // Second column takes 60% of the width - - var row = 0; - var allControls = new Dictionary>(); - - // Add general settings section - var generalHeader = new Label - { - Text = "General Settings", - Font = new Font(form.Font, FontStyle.Bold), - Dock = DockStyle.Fill, - Padding = new Padding(0, 15, 0, 10), - AutoSize = true - }; - panel.Controls.Add(generalHeader, 0, row); - panel.SetColumnSpan(generalHeader, 2); - row++; - - // Add default provider selection - panel.Controls.Add(new Label - { - Text = "Default AI Provider:", - Dock = DockStyle.Fill, - AutoSize = true - }, 0, row); + // Dictionary to store all controls for later retrieval + var allControls = new Dictionary>(); - var defaultProviderComboBox = new ComboBox - { - Dock = DockStyle.Fill, - DropDownStyle = ComboBoxStyle.DropDownList - }; - - // Add all providers to the dropdown - foreach (var provider in providers) - { - defaultProviderComboBox.Items.Add(provider.Name); - } - - // Select the current default provider if set - if (!string.IsNullOrEmpty(settings.DefaultAIProvider) && - defaultProviderComboBox.Items.Contains(settings.DefaultAIProvider)) - { - defaultProviderComboBox.SelectedItem = settings.DefaultAIProvider; - } - else if (defaultProviderComboBox.Items.Count > 0) - { - defaultProviderComboBox.SelectedIndex = 0; - } - - panel.Controls.Add(defaultProviderComboBox, 1, row); - row++; + // Add general settings section + layout.Rows.Add(new Eto.Forms.TableRow( + new Eto.Forms.TableCell(new Eto.Forms.Label + { + Text = "General Settings", + Font = new Eto.Drawing.Font(Eto.Drawing.SystemFont.Bold, 12), + VerticalAlignment = Eto.Forms.VerticalAlignment.Center + }) + )); + + // Add default provider selection + var defaultProviderRow = new Eto.Forms.TableLayout + { + Spacing = new Eto.Drawing.Size(5, 5), + Padding = new Eto.Drawing.Padding(0) + }; - // Add default provider description - var defaultProviderDescription = new Label - { - Text = "The default AI provider to use when 'Default' is selected in components", - ForeColor = SystemColors.GrayText, - Font = new Font(form.Font.FontFamily, form.Font.Size - 1), - Dock = DockStyle.Fill, - AutoSize = true, - Padding = new Padding(5, 0, 0, 5) - }; - panel.Controls.Add(defaultProviderDescription, 0, row); - panel.SetColumnSpan(defaultProviderDescription, 2); - row++; - - // Add debounce time setting - panel.Controls.Add(new Label - { - Text = "Debounce Time (ms):", - Dock = DockStyle.Fill, - AutoSize = true - }, 0, row); + var defaultProviderComboBox = new Eto.Forms.DropDown(); + + // Add all providers to the dropdown + foreach (var provider in providers) + { + defaultProviderComboBox.Items.Add(new Eto.Forms.ListItem { Text = provider.Name }); + } + + // Select the current default provider if set + if (!string.IsNullOrEmpty(settings.DefaultAIProvider)) + { + for (int i = 0; i < defaultProviderComboBox.Items.Count; i++) + { + if (defaultProviderComboBox.Items[i].Text == settings.DefaultAIProvider) + { + defaultProviderComboBox.SelectedIndex = i; + break; + } + } + } + else if (defaultProviderComboBox.Items.Count > 0) + { + defaultProviderComboBox.SelectedIndex = 0; + } - var debounceControl = new NumericUpDown - { - Minimum = 1000, - Maximum = 5000, - Value = settings.DebounceTime, - Dock = DockStyle.Fill - }; - panel.Controls.Add(debounceControl, 1, row); - row++; - - // Add debounce description - var debounceDescription = new Label - { - Text = "Time to wait before sending a new request (in milliseconds)", - ForeColor = SystemColors.GrayText, - Font = new Font(form.Font.FontFamily, form.Font.Size - 1), - Dock = DockStyle.Fill, - AutoSize = true, - Padding = new Padding(5, 0, 0, 5) - }; - panel.Controls.Add(debounceDescription, 0, row); - panel.SetColumnSpan(debounceDescription, 2); - row++; - - foreach (var provider in providers) - { - System.Diagnostics.Debug.WriteLine($"Provider: {provider.Name}"); - var descriptors = provider.GetSettingDescriptors().ToList(); - System.Diagnostics.Debug.WriteLine($"Number of descriptors: {descriptors.Count}"); + defaultProviderRow.Rows.Add(new Eto.Forms.TableRow( + new Eto.Forms.TableCell(new Eto.Forms.Label { Text = "Default AI Provider:", VerticalAlignment = Eto.Forms.VerticalAlignment.Center }), + new Eto.Forms.TableCell(defaultProviderComboBox) + )); + layout.Rows.Add(defaultProviderRow); - // Provider header - var header = new Label + // Add default provider description + layout.Rows.Add(new Eto.Forms.TableRow( + new Eto.Forms.TableCell(new Eto.Forms.Label + { + Text = "The default AI provider to use when 'Default' is selected in components", + TextColor = Eto.Drawing.Colors.Gray, + Font = new Eto.Drawing.Font(Eto.Drawing.SystemFont.Default, 10) + }) + )); + + // Add debounce time setting + var debounceRow = new Eto.Forms.TableLayout { - Text = provider.Name, - Font = new Font(form.Font, FontStyle.Bold), - Dock = DockStyle.Fill, - Padding = new Padding(0, 15, 0, 10), - AutoSize = true + Spacing = new Eto.Drawing.Size(5, 5), + Padding = new Eto.Drawing.Padding(0) }; - panel.Controls.Add(header, 0, row); - panel.SetColumnSpan(header, 2); - row++; - var controls = new Dictionary(); - foreach (var descriptor in descriptors) + var debounceControl = new Eto.Forms.NumericStepper { - System.Diagnostics.Debug.WriteLine($"Creating control for: {descriptor.Name} ({descriptor.DisplayName})"); + MinValue = 1000, + MaxValue = 5000, + Value = settings.DebounceTime + }; - // Add label and control - panel.Controls.Add(new Label + debounceRow.Rows.Add(new Eto.Forms.TableRow( + new Eto.Forms.TableCell(new Eto.Forms.Label { Text = "Debounce Time (ms):", VerticalAlignment = Eto.Forms.VerticalAlignment.Center }), + new Eto.Forms.TableCell(debounceControl) + )); + layout.Rows.Add(debounceRow); + + // Add debounce description + layout.Rows.Add(new Eto.Forms.TableRow( + new Eto.Forms.TableCell(new Eto.Forms.Label { - Text = descriptor.DisplayName + ":", - Dock = DockStyle.Fill, - AutoSize = true - }, 0, row); - - var control = ControlFactories[descriptor.Type](descriptor); - panel.Controls.Add(control, 1, row); - controls[descriptor.Name] = control; - row++; - - // Add description - if (!string.IsNullOrWhiteSpace(descriptor.Description)) + Text = "Time to wait before sending a new request (in milliseconds)", + TextColor = Eto.Drawing.Colors.Gray, + Font = new Eto.Drawing.Font(Eto.Drawing.SystemFont.Default, 10) + }) + )); + + // Add provider settings + foreach (var provider in providers) + { + var descriptors = provider.GetSettingDescriptors().ToList(); + + // Create a row for the provider header with icon + var headerLayout = new Eto.Forms.StackLayout { - var descriptionLabel = new Label + Orientation = Eto.Forms.Orientation.Horizontal, + Spacing = 5, + VerticalContentAlignment = Eto.Forms.VerticalAlignment.Center, + Padding = new Eto.Drawing.Padding(0, 15, 0, 10) + }; + + // Add provider icon + if (provider.Icon != null) + { + // Convert System.Drawing.Image to Eto.Drawing.Image + using (var ms = new System.IO.MemoryStream()) { - Text = descriptor.Description, - ForeColor = SystemColors.GrayText, - Font = new Font(form.Font.FontFamily, form.Font.Size - 1), - Dock = DockStyle.Fill, - AutoSize = true, - Padding = new Padding(5, 0, 0, 5) - }; - panel.Controls.Add(descriptionLabel, 0, row); - panel.SetColumnSpan(descriptionLabel, 2); - row++; + provider.Icon.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + ms.Position = 0; + var etoImage = new Eto.Drawing.Bitmap(ms); + var imageView = new Eto.Forms.ImageView + { + Image = etoImage, + Size = new Eto.Drawing.Size(16, 16) + }; + headerLayout.Items.Add(imageView); + } } - // Load current value if exists - if (settings.ProviderSettings.ContainsKey(provider.Name) && - settings.ProviderSettings[provider.Name].ContainsKey(descriptor.Name)) + // Add provider name + headerLayout.Items.Add(new Eto.Forms.Label { - var value = settings.ProviderSettings[provider.Name][descriptor.Name]; - if (control is TextBox textBox) - textBox.Text = value?.ToString() ?? ""; - else if (control is NumericUpDown numericUpDown && value != null) - numericUpDown.Value = Convert.ToInt32(value); - } - else if (descriptor.DefaultValue != null) + Text = provider.Name, + Font = new Eto.Drawing.Font(Eto.Drawing.SystemFont.Bold, 12), + VerticalAlignment = Eto.Forms.VerticalAlignment.Center + }); + + layout.Rows.Add(new Eto.Forms.TableRow(new Eto.Forms.TableCell(headerLayout))); + + var controls = new Dictionary(); + foreach (var descriptor in descriptors) { - if (control is TextBox textBox) - textBox.Text = descriptor.DefaultValue.ToString(); - else if (control is NumericUpDown numericUpDown) - numericUpDown.Value = Convert.ToInt32(descriptor.DefaultValue); + // Create a row for each setting + var settingRow = new Eto.Forms.TableLayout + { + Spacing = new Eto.Drawing.Size(5, 5), + Padding = new Eto.Drawing.Padding(0) + }; + + // Create the control based on the descriptor type + var control = ControlFactories[descriptor.Type](descriptor); + + settingRow.Rows.Add(new Eto.Forms.TableRow( + new Eto.Forms.TableCell(new Eto.Forms.Label { Text = descriptor.DisplayName + ":", VerticalAlignment = Eto.Forms.VerticalAlignment.Center }), + new Eto.Forms.TableCell(control) + )); + layout.Rows.Add(settingRow); + + controls[descriptor.Name] = control; + + // Add description if available + if (!string.IsNullOrWhiteSpace(descriptor.Description)) + { + layout.Rows.Add(new Eto.Forms.TableRow( + new Eto.Forms.TableCell(new Eto.Forms.Label + { + Text = descriptor.Description, + TextColor = Eto.Drawing.Colors.Gray, + Font = new Eto.Drawing.Font(Eto.Drawing.SystemFont.Default, 10) + }) + )); + } + + // Load current value if exists + if (settings.ProviderSettings.ContainsKey(provider.Name) && + settings.ProviderSettings[provider.Name].ContainsKey(descriptor.Name)) + { + var value = settings.ProviderSettings[provider.Name][descriptor.Name]; + if (control is Eto.Forms.TextBox textBox) + textBox.Text = value?.ToString() ?? ""; + else if (control is Eto.Forms.NumericStepper numericStepper && value != null) + numericStepper.Value = Convert.ToInt32(value); + } + else if (descriptor.DefaultValue != null) + { + if (control is Eto.Forms.TextBox textBox) + textBox.Text = descriptor.DefaultValue.ToString(); + else if (control is Eto.Forms.NumericStepper numericStepper) + numericStepper.Value = Convert.ToInt32(descriptor.DefaultValue); + } } - } - allControls[provider.Name] = controls; - } + allControls[provider.Name] = controls; + } - // Add save button at the bottom - var buttonPanel = new Panel - { - Dock = DockStyle.Bottom, - Height = 40, - Padding = new Padding(5) - }; + // Add a spacer row at the end + layout.Rows.Add(Eto.Forms.TableLayout.AutoSized(null)); - var saveButton = new Button - { - Text = "Save", - DialogResult = DialogResult.OK, - Dock = DockStyle.Right - }; - buttonPanel.Controls.Add(saveButton); - form.Controls.Add(buttonPanel); - form.AcceptButton = saveButton; - - if (form.ShowDialog() == DialogResult.OK) - { - // Save settings - foreach (var provider in providers) + // Create buttons + var buttonLayout = new Eto.Forms.StackLayout { - if (!settings.ProviderSettings.ContainsKey(provider.Name)) - settings.ProviderSettings[provider.Name] = new Dictionary(); + Orientation = Eto.Forms.Orientation.Horizontal, + Spacing = 5, + HorizontalContentAlignment = Eto.Forms.HorizontalAlignment.Right + }; + + var saveButton = new Eto.Forms.Button { Text = "Save" }; + var cancelButton = new Eto.Forms.Button { Text = "Cancel" }; - var controls = allControls[provider.Name]; - foreach (var descriptor in provider.GetSettingDescriptors()) + buttonLayout.Items.Add(new Eto.Forms.StackLayoutItem(null, true)); // Spacer + buttonLayout.Items.Add(saveButton); + buttonLayout.Items.Add(cancelButton); + + // Set up the dialog content + var content = new Eto.Forms.DynamicLayout(); + content.Add(scrollable, yscale: true); + content.Add(buttonLayout); + + dialog.Content = content; + dialog.DefaultButton = saveButton; + dialog.AbortButton = cancelButton; + + // Handle button clicks + saveButton.Click += (sender, e) => + { + // Save settings + foreach (var provider in providers) { - var control = controls[descriptor.Name]; - object value = null; + if (!settings.ProviderSettings.ContainsKey(provider.Name)) + settings.ProviderSettings[provider.Name] = new Dictionary(); - if (control is TextBox textBox) - value = textBox.Text; - else if (control is NumericUpDown numericUpDown) - value = (int)numericUpDown.Value; + var controls = allControls[provider.Name]; + foreach (var descriptor in provider.GetSettingDescriptors()) + { + var control = controls[descriptor.Name]; + object value = null; - settings.ProviderSettings[provider.Name][descriptor.Name] = value; + if (control is Eto.Forms.TextBox textBox) + value = textBox.Text; + else if (control is Eto.Forms.NumericStepper numericStepper) + value = (int)numericStepper.Value; + + settings.ProviderSettings[provider.Name][descriptor.Name] = value; + } } - } - // Save debounce time - settings.DebounceTime = (int)debounceControl.Value; - - // Save default provider - settings.DefaultAIProvider = defaultProviderComboBox.SelectedItem?.ToString() ?? ""; + // Save debounce time + settings.DebounceTime = (int)debounceControl.Value; + + // Save default provider + settings.DefaultAIProvider = defaultProviderComboBox.SelectedValue?.ToString() ?? ""; + if (string.IsNullOrEmpty(settings.DefaultAIProvider) && defaultProviderComboBox.SelectedIndex >= 0) + { + settings.DefaultAIProvider = defaultProviderComboBox.Items[defaultProviderComboBox.SelectedIndex].Text; + } - settings.Save(); + settings.Save(); + dialog.Close(); + }; + + cancelButton.Click += (sender, e) => dialog.Close(); + + // Show the dialog + dialog.ShowModal(); } - } + })); } } } From 8eb2dc58f441c44ab07eb1b0fbb311b03810651d Mon Sep 17 00:00:00 2001 From: marc-romu <49920661+marc-romu@users.noreply.github.com> Date: Sun, 30 Mar 2025 21:52:38 +0200 Subject: [PATCH 3/8] docs --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4cacb69..9aa9bf9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Automatically build and attach artifacts to published releases. - Create platform-specific zip files (Rhino8-Windows, Rhino8-Mac) instead of a single zip with subfolders. - Improved error handling in the AIStatefulAsyncComponentBase. +- Updated settings menu to use Eto.Forms and Eto.Drawing. ### Removed From 07be0d8f3a141e947cdce4a57b7bea6bcf5596d1 Mon Sep 17 00:00:00 2001 From: marc-romu <49920661+marc-romu@users.noreply.github.com> Date: Sun, 30 Mar 2025 22:07:18 +0200 Subject: [PATCH 4/8] fix(settings): error storing api key --- .../Items/SettingsMenuItem.cs | 110 ++++++++++++++++-- 1 file changed, 101 insertions(+), 9 deletions(-) diff --git a/src/SmartHopper.Menu/Items/SettingsMenuItem.cs b/src/SmartHopper.Menu/Items/SettingsMenuItem.cs index a1ca6e88..8ec5ac99 100644 --- a/src/SmartHopper.Menu/Items/SettingsMenuItem.cs +++ b/src/SmartHopper.Menu/Items/SettingsMenuItem.cs @@ -10,9 +10,12 @@ using SmartHopper.Config.Configuration; using SmartHopper.Config.Models; +using SmartHopper.Config.Providers; using System; using System.Collections.Generic; using System.Linq; +using System.Windows.Forms; +using System.Drawing; using Rhino; namespace SmartHopper.Menu.Items @@ -21,10 +24,17 @@ internal static class SettingsMenuItem { private static readonly Dictionary> ControlFactories = new Dictionary> { - [typeof(string)] = descriptor => new Eto.Forms.TextBox + [typeof(string)] = descriptor => { - // Use ReadOnly for passwords - ReadOnly = descriptor.IsSecret + // Use PasswordBox for secret fields, TextBox for regular text + if (descriptor.IsSecret) + { + return new Eto.Forms.PasswordBox(); + } + else + { + return new Eto.Forms.TextBox(); + } }, [typeof(int)] = descriptor => new Eto.Forms.NumericStepper { @@ -50,7 +60,15 @@ private static void ShowSettingsDialog() { dialog.Title = "SmartHopper Settings"; dialog.Size = new Eto.Drawing.Size(500, 400); + dialog.MinimumSize = new Eto.Drawing.Size(400, 300); + dialog.Resizable = true; dialog.Padding = new Eto.Drawing.Padding(10); + + // Center the dialog on screen + dialog.Location = new Eto.Drawing.Point( + (int)((Eto.Forms.Screen.PrimaryScreen.Bounds.Width - dialog.Size.Width) / 2), + (int)((Eto.Forms.Screen.PrimaryScreen.Bounds.Height - dialog.Size.Height) / 2) + ); var providers = SmartHopperSettings.DiscoverProviders().ToArray(); var settings = SmartHopperSettings.Load(); @@ -69,6 +87,8 @@ private static void ShowSettingsDialog() // Dictionary to store all controls for later retrieval var allControls = new Dictionary>(); + // Dictionary to track original API key values to avoid overwriting unchanged values + var originalApiKeys = new Dictionary>(); // Add general settings section layout.Rows.Add(new Eto.Forms.TableRow( @@ -201,6 +221,8 @@ private static void ShowSettingsDialog() layout.Rows.Add(new Eto.Forms.TableRow(new Eto.Forms.TableCell(headerLayout))); var controls = new Dictionary(); + originalApiKeys[provider.Name] = new Dictionary(); + foreach (var descriptor in descriptors) { // Create a row for each setting @@ -239,17 +261,49 @@ private static void ShowSettingsDialog() settings.ProviderSettings[provider.Name].ContainsKey(descriptor.Name)) { var value = settings.ProviderSettings[provider.Name][descriptor.Name]; + if (control is Eto.Forms.TextBox textBox) + { textBox.Text = value?.ToString() ?? ""; + + // Store original value for API keys + if (descriptor.IsSecret) + { + originalApiKeys[provider.Name][descriptor.Name] = textBox.Text; + } + } + else if (control is Eto.Forms.PasswordBox passwordBox) + { + passwordBox.Text = value?.ToString() ?? ""; + + // Store original value for API keys + originalApiKeys[provider.Name][descriptor.Name] = passwordBox.Text; + } else if (control is Eto.Forms.NumericStepper numericStepper && value != null) + { numericStepper.Value = Convert.ToInt32(value); + } } else if (descriptor.DefaultValue != null) { if (control is Eto.Forms.TextBox textBox) + { textBox.Text = descriptor.DefaultValue.ToString(); + } + else if (control is Eto.Forms.PasswordBox passwordBox) + { + passwordBox.Text = descriptor.DefaultValue.ToString(); + + // Store original value for API keys + if (descriptor.IsSecret) + { + originalApiKeys[provider.Name][descriptor.Name] = passwordBox.Text; + } + } else if (control is Eto.Forms.NumericStepper numericStepper) + { numericStepper.Value = Convert.ToInt32(descriptor.DefaultValue); + } } } @@ -298,12 +352,50 @@ private static void ShowSettingsDialog() var control = controls[descriptor.Name]; object value = null; - if (control is Eto.Forms.TextBox textBox) - value = textBox.Text; - else if (control is Eto.Forms.NumericStepper numericStepper) - value = (int)numericStepper.Value; - - settings.ProviderSettings[provider.Name][descriptor.Name] = value; + // Only update API keys if they've changed + if (descriptor.IsSecret) + { + string newValue = null; + + if (control is Eto.Forms.TextBox textBox) + newValue = textBox.Text; + else if (control is Eto.Forms.PasswordBox passwordBox) + newValue = passwordBox.Text; + + // Check if API key has changed + if (string.IsNullOrEmpty(newValue)) + { + // If empty, don't update (keep existing value) + continue; + } + else if (originalApiKeys[provider.Name].ContainsKey(descriptor.Name) && + newValue == originalApiKeys[provider.Name][descriptor.Name]) + { + // If unchanged, don't update + continue; + } + else + { + // Value has changed, update it + value = newValue; + } + } + else + { + // For non-secret fields, always update + if (control is Eto.Forms.TextBox textBox) + value = textBox.Text; + else if (control is Eto.Forms.PasswordBox passwordBox) + value = passwordBox.Text; + else if (control is Eto.Forms.NumericStepper numericStepper) + value = (int)numericStepper.Value; + } + + // Only update if we have a value to set + if (value != null) + { + settings.ProviderSettings[provider.Name][descriptor.Name] = value; + } } } From a7da3ff6e0d7ffe5f8b8bab9bfa3d935b5afcf47 Mon Sep 17 00:00:00 2001 From: marc-romu <49920661+marc-romu@users.noreply.github.com> Date: Sun, 30 Mar 2025 22:16:04 +0200 Subject: [PATCH 5/8] refactor(settings): simplification of the settings menu --- .../Items/SettingsMenuItem.cs | 226 ++++++------------ 1 file changed, 76 insertions(+), 150 deletions(-) diff --git a/src/SmartHopper.Menu/Items/SettingsMenuItem.cs b/src/SmartHopper.Menu/Items/SettingsMenuItem.cs index 8ec5ac99..2844d226 100644 --- a/src/SmartHopper.Menu/Items/SettingsMenuItem.cs +++ b/src/SmartHopper.Menu/Items/SettingsMenuItem.cs @@ -10,12 +10,9 @@ using SmartHopper.Config.Configuration; using SmartHopper.Config.Models; -using SmartHopper.Config.Providers; using System; using System.Collections.Generic; using System.Linq; -using System.Windows.Forms; -using System.Drawing; using Rhino; namespace SmartHopper.Menu.Items @@ -26,15 +23,10 @@ internal static class SettingsMenuItem { [typeof(string)] = descriptor => { - // Use PasswordBox for secret fields, TextBox for regular text if (descriptor.IsSecret) - { return new Eto.Forms.PasswordBox(); - } else - { return new Eto.Forms.TextBox(); - } }, [typeof(int)] = descriptor => new Eto.Forms.NumericStepper { @@ -74,21 +66,13 @@ private static void ShowSettingsDialog() var settings = SmartHopperSettings.Load(); // Create the main layout - var layout = new Eto.Forms.TableLayout - { - Spacing = new Eto.Drawing.Size(5, 5), - Padding = new Eto.Drawing.Padding(10) - }; - - var scrollable = new Eto.Forms.Scrollable - { - Content = layout - }; + var layout = new Eto.Forms.TableLayout { Spacing = new Eto.Drawing.Size(5, 5), Padding = new Eto.Drawing.Padding(10) }; + var scrollable = new Eto.Forms.Scrollable { Content = layout }; // Dictionary to store all controls for later retrieval var allControls = new Dictionary>(); - // Dictionary to track original API key values to avoid overwriting unchanged values - var originalApiKeys = new Dictionary>(); + // Dictionary to track original values to avoid overwriting unchanged sensitive data + var originalValues = new Dictionary>(); // Add general settings section layout.Rows.Add(new Eto.Forms.TableRow( @@ -101,21 +85,15 @@ private static void ShowSettingsDialog() )); // Add default provider selection - var defaultProviderRow = new Eto.Forms.TableLayout - { - Spacing = new Eto.Drawing.Size(5, 5), - Padding = new Eto.Drawing.Padding(0) - }; - + var defaultProviderRow = new Eto.Forms.TableLayout { Spacing = new Eto.Drawing.Size(5, 5) }; var defaultProviderComboBox = new Eto.Forms.DropDown(); - // Add all providers to the dropdown + // Add all providers to the dropdown and select current default foreach (var provider in providers) { defaultProviderComboBox.Items.Add(new Eto.Forms.ListItem { Text = provider.Name }); } - // Select the current default provider if set if (!string.IsNullOrEmpty(settings.DefaultAIProvider)) { for (int i = 0; i < defaultProviderComboBox.Items.Count; i++) @@ -142,19 +120,14 @@ private static void ShowSettingsDialog() layout.Rows.Add(new Eto.Forms.TableRow( new Eto.Forms.TableCell(new Eto.Forms.Label { - Text = "The default AI provider to use when 'Default' is selected in components", + Text = "The default AI provider to use when the 'Default' provider is selected", TextColor = Eto.Drawing.Colors.Gray, Font = new Eto.Drawing.Font(Eto.Drawing.SystemFont.Default, 10) }) )); // Add debounce time setting - var debounceRow = new Eto.Forms.TableLayout - { - Spacing = new Eto.Drawing.Size(5, 5), - Padding = new Eto.Drawing.Padding(0) - }; - + var debounceRow = new Eto.Forms.TableLayout { Spacing = new Eto.Drawing.Size(5, 5) }; var debounceControl = new Eto.Forms.NumericStepper { MinValue = 1000, @@ -182,8 +155,10 @@ private static void ShowSettingsDialog() foreach (var provider in providers) { var descriptors = provider.GetSettingDescriptors().ToList(); + var controls = new Dictionary(); + originalValues[provider.Name] = new Dictionary(); - // Create a row for the provider header with icon + // Create provider header with icon var headerLayout = new Eto.Forms.StackLayout { Orientation = Eto.Forms.Orientation.Horizontal, @@ -192,21 +167,18 @@ private static void ShowSettingsDialog() Padding = new Eto.Drawing.Padding(0, 15, 0, 10) }; - // Add provider icon + // Add provider icon if available if (provider.Icon != null) { - // Convert System.Drawing.Image to Eto.Drawing.Image using (var ms = new System.IO.MemoryStream()) { provider.Icon.Save(ms, System.Drawing.Imaging.ImageFormat.Png); ms.Position = 0; - var etoImage = new Eto.Drawing.Bitmap(ms); - var imageView = new Eto.Forms.ImageView + headerLayout.Items.Add(new Eto.Forms.ImageView { - Image = etoImage, + Image = new Eto.Drawing.Bitmap(ms), Size = new Eto.Drawing.Size(16, 16) - }; - headerLayout.Items.Add(imageView); + }); } } @@ -220,29 +192,24 @@ private static void ShowSettingsDialog() layout.Rows.Add(new Eto.Forms.TableRow(new Eto.Forms.TableCell(headerLayout))); - var controls = new Dictionary(); - originalApiKeys[provider.Name] = new Dictionary(); - + // Add settings for this provider foreach (var descriptor in descriptors) { - // Create a row for each setting - var settingRow = new Eto.Forms.TableLayout - { - Spacing = new Eto.Drawing.Size(5, 5), - Padding = new Eto.Drawing.Padding(0) - }; - - // Create the control based on the descriptor type + // Create control for this setting var control = ControlFactories[descriptor.Type](descriptor); + controls[descriptor.Name] = control; + // Add label and control + var settingRow = new Eto.Forms.TableLayout { Spacing = new Eto.Drawing.Size(5, 5) }; settingRow.Rows.Add(new Eto.Forms.TableRow( - new Eto.Forms.TableCell(new Eto.Forms.Label { Text = descriptor.DisplayName + ":", VerticalAlignment = Eto.Forms.VerticalAlignment.Center }), + new Eto.Forms.TableCell(new Eto.Forms.Label { + Text = descriptor.DisplayName + ":", + VerticalAlignment = Eto.Forms.VerticalAlignment.Center + }), new Eto.Forms.TableCell(control) )); layout.Rows.Add(settingRow); - controls[descriptor.Name] = control; - // Add description if available if (!string.IsNullOrWhiteSpace(descriptor.Description)) { @@ -256,54 +223,30 @@ private static void ShowSettingsDialog() )); } - // Load current value if exists + // Load current value + string currentValue = null; if (settings.ProviderSettings.ContainsKey(provider.Name) && settings.ProviderSettings[provider.Name].ContainsKey(descriptor.Name)) { - var value = settings.ProviderSettings[provider.Name][descriptor.Name]; - - if (control is Eto.Forms.TextBox textBox) - { - textBox.Text = value?.ToString() ?? ""; - - // Store original value for API keys - if (descriptor.IsSecret) - { - originalApiKeys[provider.Name][descriptor.Name] = textBox.Text; - } - } - else if (control is Eto.Forms.PasswordBox passwordBox) - { - passwordBox.Text = value?.ToString() ?? ""; - - // Store original value for API keys - originalApiKeys[provider.Name][descriptor.Name] = passwordBox.Text; - } - else if (control is Eto.Forms.NumericStepper numericStepper && value != null) - { - numericStepper.Value = Convert.ToInt32(value); - } + currentValue = settings.ProviderSettings[provider.Name][descriptor.Name]?.ToString(); } else if (descriptor.DefaultValue != null) + { + currentValue = descriptor.DefaultValue.ToString(); + } + + // Set value to control and store original for comparison + if (currentValue != null) { if (control is Eto.Forms.TextBox textBox) - { - textBox.Text = descriptor.DefaultValue.ToString(); - } + textBox.Text = currentValue; else if (control is Eto.Forms.PasswordBox passwordBox) - { - passwordBox.Text = descriptor.DefaultValue.ToString(); - - // Store original value for API keys - if (descriptor.IsSecret) - { - originalApiKeys[provider.Name][descriptor.Name] = passwordBox.Text; - } - } + passwordBox.Text = currentValue; else if (control is Eto.Forms.NumericStepper numericStepper) - { - numericStepper.Value = Convert.ToInt32(descriptor.DefaultValue); - } + numericStepper.Value = Convert.ToInt32(currentValue); + + // Store original value for comparison + originalValues[provider.Name][descriptor.Name] = currentValue; } } @@ -340,75 +283,58 @@ private static void ShowSettingsDialog() // Handle button clicks saveButton.Click += (sender, e) => { - // Save settings + // Create a copy of the current settings to preserve encrypted values + var updatedSettings = new Dictionary>(); + foreach (var providerSetting in settings.ProviderSettings) + { + updatedSettings[providerSetting.Key] = new Dictionary(providerSetting.Value); + } + + // Update with new values from the UI foreach (var provider in providers) { - if (!settings.ProviderSettings.ContainsKey(provider.Name)) - settings.ProviderSettings[provider.Name] = new Dictionary(); + if (!updatedSettings.ContainsKey(provider.Name)) + updatedSettings[provider.Name] = new Dictionary(); var controls = allControls[provider.Name]; foreach (var descriptor in provider.GetSettingDescriptors()) { var control = controls[descriptor.Name]; - object value = null; - - // Only update API keys if they've changed - if (descriptor.IsSecret) - { - string newValue = null; - - if (control is Eto.Forms.TextBox textBox) - newValue = textBox.Text; - else if (control is Eto.Forms.PasswordBox passwordBox) - newValue = passwordBox.Text; - - // Check if API key has changed - if (string.IsNullOrEmpty(newValue)) - { - // If empty, don't update (keep existing value) - continue; - } - else if (originalApiKeys[provider.Name].ContainsKey(descriptor.Name) && - newValue == originalApiKeys[provider.Name][descriptor.Name]) - { - // If unchanged, don't update - continue; - } - else - { - // Value has changed, update it - value = newValue; - } - } - else - { - // For non-secret fields, always update - if (control is Eto.Forms.TextBox textBox) - value = textBox.Text; - else if (control is Eto.Forms.PasswordBox passwordBox) - value = passwordBox.Text; - else if (control is Eto.Forms.NumericStepper numericStepper) - value = (int)numericStepper.Value; - } - - // Only update if we have a value to set - if (value != null) + + // Get new value from control + object newValue = null; + if (control is Eto.Forms.TextBox textBox) + newValue = textBox.Text; + else if (control is Eto.Forms.PasswordBox passwordBox) + newValue = passwordBox.Text; + else if (control is Eto.Forms.NumericStepper numericStepper) + newValue = (int)numericStepper.Value; + + // For sensitive data, only update if changed and not empty + if (descriptor.IsSecret && newValue is string strValue) { - settings.ProviderSettings[provider.Name][descriptor.Name] = value; + if (string.IsNullOrEmpty(strValue)) + continue; // Keep existing value + + if (originalValues[provider.Name].ContainsKey(descriptor.Name) && + strValue == originalValues[provider.Name][descriptor.Name]) + continue; // Skip unchanged values } + + // Update the setting + updatedSettings[provider.Name][descriptor.Name] = newValue; } } - - // Save debounce time + + // Update settings + settings.ProviderSettings = updatedSettings; settings.DebounceTime = (int)debounceControl.Value; // Save default provider - settings.DefaultAIProvider = defaultProviderComboBox.SelectedValue?.ToString() ?? ""; - if (string.IsNullOrEmpty(settings.DefaultAIProvider) && defaultProviderComboBox.SelectedIndex >= 0) - { + if (defaultProviderComboBox.SelectedIndex >= 0) settings.DefaultAIProvider = defaultProviderComboBox.Items[defaultProviderComboBox.SelectedIndex].Text; - } - + + // Save settings (this will handle encryption) settings.Save(); dialog.Close(); }; From 9a64fc3b3d1795fda621bdf7c09f0edd00a1a791 Mon Sep 17 00:00:00 2001 From: marc-romu <49920661+marc-romu@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:34:47 +0200 Subject: [PATCH 6/8] chore(version): switch to 0.2.0-dev since there are many new features here --- .github/actions/version-tools/action.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/version-tools/action.yml b/.github/actions/version-tools/action.yml index 7f2135ce..0191a675 100644 --- a/.github/actions/version-tools/action.yml +++ b/.github/actions/version-tools/action.yml @@ -104,8 +104,8 @@ runs: # Determine color and status based on version if [[ $VERSION == *"-dev"* ]]; then - COLOR="red" - STATUS="Unstable Development" + COLOR="brown" + STATUS="Unstable%20Development" elif [[ $VERSION == *"-alpha"* ]]; then COLOR="orange" STATUS="Alpha" @@ -113,10 +113,10 @@ runs: COLOR="yellow" STATUS="Beta" elif [[ $VERSION == *"-rc"* ]]; then - COLOR="blue" - STATUS="Release Candidate" + COLOR="lightblue" + STATUS="Release%20Candidate" else - COLOR="brightgreen" + COLOR="lightgreen" STATUS="Stable" fi From a8508df7853471fd5ebe0a90fb95fa99d9677db7 Mon Sep 17 00:00:00 2001 From: marc-romu <49920661+marc-romu@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:35:37 +0200 Subject: [PATCH 7/8] chore(version): switch to 0.2.0-dev since there are many new features here --- README.md | 4 ++-- Solution.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 01f55b3f..88cf4bda 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # SmartHopper - AI-Powered Grasshopper3D Plugin -[![Version](https://img.shields.io/badge/version-0%2E1%2E3--dev%2E250330-red)](https://github.com/architects-toolkit/SmartHopper/releases) -[![Status](https://img.shields.io/badge/status-Development-red)](https://github.com/architects-toolkit/SmartHopper/releases) +[![Version](https://img.shields.io/badge/version-0%2E2%2E0--dev%2E250331-red)](https://github.com/architects-toolkit/SmartHopper/releases) +[![Status](https://img.shields.io/badge/status-Unstable%20Development-red)](https://github.com/architects-toolkit/SmartHopper/releases) [![Grasshopper](https://img.shields.io/badge/plugin_for-Grasshopper3D-darkgreen?logo=rhinoceros)](https://www.rhino3d.com/) [![MistralAI](https://img.shields.io/badge/AI--powered-MistralAI-orange)](https://mistral.ai/) [![OpenAI](https://img.shields.io/badge/AI--powered-OpenAI-blue?logo=openai)](https://openai.com/) diff --git a/Solution.props b/Solution.props index c7f00988..48d0126a 100644 --- a/Solution.props +++ b/Solution.props @@ -1,5 +1,5 @@ - 0.1.3-dev.250330 + 0.2.0-dev.250331 \ No newline at end of file From e4634bc4e8d82be5a848e6537f195e337aaabd6f Mon Sep 17 00:00:00 2001 From: marc-romu <49920661+marc-romu@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:36:43 +0200 Subject: [PATCH 8/8] chore(workflow): Improved version badge workflow to also update badges when color doesn't match the requirements based on version type --- .github/actions/version-tools/action.yml | 62 +++++++++++------------- CHANGELOG.md | 1 + 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/.github/actions/version-tools/action.yml b/.github/actions/version-tools/action.yml index 0191a675..2f19d05e 100644 --- a/.github/actions/version-tools/action.yml +++ b/.github/actions/version-tools/action.yml @@ -131,56 +131,50 @@ runs: echo "New version badge: $VERSION_BADGE" echo "New status badge: $STATUS_BADGE" + BADGES_CHANGED=false + # Update README.md with new badges if [[ -f "README.md" ]]; then - BADGES_CHANGED=false - - # Get the current version from Solution.props - echo "Current version from Solution.props: $VERSION" - - # Create new badge URLs - VERSION_BADGE_URL="https://img.shields.io/badge/version-$ENCODED_VERSION-$COLOR" - STATUS_BADGE_URL="https://img.shields.io/badge/status-$STATUS-$COLOR" - - # Create badge markdown - VERSION_BADGE="[![Version]($VERSION_BADGE_URL)](https://github.com/architects-toolkit/SmartHopper/releases)" - STATUS_BADGE="[![Status]($STATUS_BADGE_URL)](https://github.com/architects-toolkit/SmartHopper/releases)" - - echo "New version badge: $VERSION_BADGE" - echo "New status badge: $STATUS_BADGE" - - # Check if README contains version badge + # Check if README contains version badge and if it needs updating if grep -q "\[\!\[Version\]" README.md; then echo "Found version badge in README" - # Always update the badges with the current version from Solution.props - sed -i "s|\[\!\[Version\](https://img\.shields\.io/badge/version[^)]*)|[![Version]($VERSION_BADGE_URL)|g" README.md - echo "Updated version badge" - BADGES_CHANGED=true + # Extract current badge URL to check if color matches + CURRENT_VERSION_BADGE=$(grep -o "\[\!\[Version\](https://img\.shields\.io/badge/version[^)]*)" README.md || echo "") + + # Check if the badge needs updating (different version or color) + if [[ "$CURRENT_VERSION_BADGE" != *"$ENCODED_VERSION"* || "$CURRENT_VERSION_BADGE" != *"-$COLOR"* ]]; then + sed -i "s|\[\!\[Version\](https://img\.shields\.io/badge/version[^)]*)|[![Version]($VERSION_BADGE_URL)|g" README.md + echo "Updated version badge" + BADGES_CHANGED=true + else + echo "Version badge is already up to date" + fi else echo "No version badge found in README to replace" fi - # Check if README contains status badge + # Check if README contains status badge and if it needs updating if grep -q "\[\!\[Status\]" README.md; then echo "Found status badge in README" - # Always update the status badge - sed -i "s|\[\!\[Status\](https://img\.shields\.io/badge/status[^)]*)|[![Status]($STATUS_BADGE_URL)|g" README.md - echo "Updated status badge" - BADGES_CHANGED=true + # Extract current badge URL to check if status or color matches + CURRENT_STATUS_BADGE=$(grep -o "\[\!\[Status\](https://img\.shields\.io/badge/status[^)]*)" README.md || echo "") + + # Check if the badge needs updating (different status or color) + if [[ "$CURRENT_STATUS_BADGE" != *"$STATUS"* || "$CURRENT_STATUS_BADGE" != *"-$COLOR"* ]]; then + sed -i "s|\[\!\[Status\](https://img\.shields\.io/badge/status[^)]*)|[![Status]($STATUS_BADGE_URL)|g" README.md + echo "Updated status badge" + BADGES_CHANGED=true + else + echo "Status badge is already up to date" + fi else echo "No status badge found in README to replace" fi - # Check if badges were changed - if [ "$BADGES_CHANGED" = true ]; then - echo "README.md badges were updated" - echo "badges-changed=true" >> $GITHUB_OUTPUT - else - echo "No changes to README.md badges" - echo "badges-changed=false" >> $GITHUB_OUTPUT - fi + # Output whether badges were changed + echo "badges-changed=$BADGES_CHANGED" >> $GITHUB_OUTPUT else echo "README.md not found, skipping badge update" echo "badges-changed=false" >> $GITHUB_OUTPUT diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa9bf9c..24a88814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Modified AIChatComponent to always run when the Run parameter is true, regardless of input changes. +- Improved version badge workflow to also update badges when color doesn't match the requirements based on version type. - Improved ChatDialog UI with numerous enhancements: - Modern chat-like interface featuring message bubbles and visual styling. - Better layout with proper text wrapping to prevent horizontal scrolling.