From 17374763f6d5122b75c130eeefd616060b440690 Mon Sep 17 00:00:00 2001 From: CorrectRoadH Date: Fri, 21 Apr 2023 14:00:18 +0800 Subject: [PATCH 001/237] fix: current coversation not respond (#50) * fix the change assistant didn't take effect immediately #45 * Update src/store/conversation.ts Co-authored-by: boojack * change method name * change method name * fix the call method * add current * merge --------- Co-authored-by: boojack --- src/components/ConversationView/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 4828123c..59dc0b69 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -105,6 +105,7 @@ const ConversationView = () => { }, [currentConversation, connectionStore.currentConnectionCtx]); const sendMessageToCurrentConversation = async () => { + const currentConversation = conversationStore.getConversationById(conversationStore.getState().currentConversationId) if (!currentConversation) { return; } From 28188d141ca72557740613f5790f9a799dc78d76 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Fri, 21 Apr 2023 14:18:48 +0800 Subject: [PATCH 002/237] chore: readme on IP whitelisting --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0498605c..cbd94be9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,13 @@ SQL Chat is built by Next.js, it supports the following databases and will add m - Only the database schema will be sent to the OpenAI API. No table data will be sent there. -## Deploy with Docker +## IP Whitelisting + +If you use [sqlchat.ai](https://sqlchat.ai) to connect to your database, you need to add 0.0.0.0 (allow all connections) +to the database whitelist IP. Because sqlchat.ai is hosted on Vercel which [uses dynamic IP](https://vercel.com/guides/how-to-allowlist-deployment-ip-address). +If this is a concern, please consider the self-host option below. + +## Self-host with Docker ```bash docker run --name sqlchat --platform linux/amd64 -p 3000:3000 sqlchat/sqlchat @@ -66,7 +72,7 @@ docker run --name sqlchat --platform linux/amd64 --env OPENAI_API_KEY=xxx --env pnpm i && pnpm dev ``` -## Common questions +## Common Questions
How to self host SQL Chat?

From 20389c5b294cb2ccc2fd49ddf3688b4142e48e2e Mon Sep 17 00:00:00 2001 From: Tianzhou Chen Date: Sat, 22 Apr 2023 17:22:50 +0800 Subject: [PATCH 003/237] chore: update wechat qr code --- public/wechat-qrcode.webp | Bin 33090 -> 30024 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/wechat-qrcode.webp b/public/wechat-qrcode.webp index 92ded4d763a7ae95c80a778b08774de22487ee69..49aff86d57d43d768ce340265cb02bcbddccd152 100644 GIT binary patch literal 30024 zcmV($K;yqsNk&FMbpQZYMM6+kP&gnobpQYmC<2`UDvAP%0zQ#Cn@XjkrXnRUJxIV3 z328%3)MuE{12GG?PFFF-FE~7G`zh0}Zr5Y&yGRG<$E_Ej4dVYm4=f*Kzqk)-ty8}{ ze{cU8`t9#O=YPTfi2QT@_y1e`$Gkt2{jd4|$j|W~`M=J6{b6t9JR15~_s{S@v0vVQ zdw%^)u}6`Cqrsupi`~(tp$cfd3Eg&;Cyxzp8)V{h;^={wMvT{^$As^S`}6 zQa|bc^8Z))Q}74$zxaRsf71L8|2qEZ|6}}b+uojg`;OtsoYn~5qU|IURnMON;ZOcrG6F_`Vxd(|P~w;h&xjFvnCa>V z1_$(r!fFRb`pu$35NPWL^U4eJc%$eH$0e2&_FCw(J*+shMq2l2e%~cVGtl2LX6ngbed0B3tsy_um1}sU%vEx&9Qbp z1^skG377IknKN<--n4fEDoqI_V)WHSMg`(!93=H@6Wf4r{Syxh^!EnweM+*w6ZIO% zeasKleYOFc3n9Uda0p1@mvu%7w?zIpW=+ubTF|a@sU|jM$<(YX5UhzuC~(at;0_vh zT&B@8ElIgT*=|>kcG{dRTv}w~_Ds|Nf`R^uewOG}7)JunFOD$mVV%G3PMHO==Y*FH z%U!uKT%^Gim>%%P(z##U6#S0>bgk+~oQy(=+zCoN(Yx8-t^-!zgc;KBvUu|RGZwP! z`w;oxs4bRLKFs<46UJ1$^2Sq0@mJA#tmBRJ<SrrbTa8MX}PReYe4INEE)<{jj z!!{cr?38-<$p$Cq1vDN+-LfgI<{ubMsi?Nz4xdt8uXh}Y4YP>phT1s#CH5t-w%WFuf@(%tvDF;tE14GPZW7#C zN%CxPk+OXh23JHqrS?DR$YkVfAH$7GV#ot5{bc)qlcn^=FYqwYDBovxp7HNRhD$t2 z@`L~%%&rWHEv9Uz+ACYOGv~`ePr|}ce>yU3td}E4dNwN%eMtkF8pB6&r4ff1*YUE3 zQ>Q`?zvJA=_WpRjW7q*DTt9(mJ?9Gj6nfrqbm-&&L`2zqN`lDRy@|3j-1Jm8DQJTL zoGTO0*_oq|Zv!9Tr=a&Q5O1J}(~jh3y8&oQarK$c!7n8tQK%38zZJMy#3pYS>>7hh z=RC<&Gkviux~8kJAfiaAk#%~7gqD&-1T2gy;vs(OO7FfQ+$QY5Qi8laBZ4B9gzy^8*8FK5qWHIg9QlBk+1;$ zFnlg+^r;r3H8rH?=Mi>)tPUd(KF-$)sO)Q>F=c8;@`h-`yNL(&4=}T;NFvu`wZ*ui zI=9#BOFZlEs@DlL#hGN)R`tpth{EzCUYqgkQT1s07 z@U=dTFAfdL??_wXb7XNnd(mFfxtv!=`}GJWc~YF1N7<5S(^&@# zWFH79L0A^)khJww5FkwZ?|nT92`Cx);=OQQXm@28bp-5z9O=_=QgzVXV$fhe*6k^RMt+#lvpuz?6&&2zW_^}fnxipEIC#_h z#o{C6^UY1Ra){|*P3FM0g9d<$jI-bM?vFTO6G77W0y=~;6H#P<{}n;Skn@b*uxmPS8Bafp=45<>eLa-W%27Bco})aEV@YenNhmJ zDAinsx=z2*rw|Z5WL#^HjtZ*fV5#!L*B>IGPx*c%o5tGW%5!G}5**dH@txODkJ*Ig zWtC6&Y4(gUg#S+u5mhbeOF8ew(ZeK#cdcUp?hD%hf9GUb5~2KsSNS;5yl6@T(47lo z-dS{9mm&UNeK(&DgZpMq@1hh}E4BPTqp<4Yr4_%|aK9Q7_2l$63^RlX-p-hbOE>MF zC-(C6u{~KB-V*_d!op9n_3OX?_WC64hd0* zIN`1kuLbwB+OGL#^Oy*mbvPja+Kqd1>W@iTaCjHSm2fTXZ}T_7(Y z&v)w!LlBGV@z;Savc0>1G>z}gq+E58m{Z5uk>O2--7fBKl!hWWt*(Ri;;^3e-L2u9l%&Su0q)CRSAFSLFznG_r#YULDHDeLpaRW>eg~}LF2k>xx+NVCru+_Kwef>wGgZ8?xvLTHcJ|tf9k%MDbpa}-x+)I){ z$&Az<65h>>kGK{J>RM3b?%hIha>#(m68pP6ok*SzikBxc9r`ECorRn3@=1`J|^%S&rLBX;vTiLg67># z7!PJyD&O^Evz%@#JQV7ZSGR~x8T;x>544s!{D2j%FR$Hv<;!YW$Ob(Rp-T6`v+5Dq z)Fjy$r${}+)dwo&pnLqomDoz=(TS1uY;^0%<!U@G61y3pg>PW~HYf>O z>yvL`Szzgc#o$^ORNjgF*s0h|}#!LHe4vr`6Zf%CCUOqhQi zrJX4w)xcwGEBVAspPY_T7)esS)){}-RknoG zq+QZIT7_;o{3?fNJW2{v;_f@{%%5VX?l?rgoZ8Z}^Uv^Ow%938WIPRKCn(cit&|+2 zL+vYT`8ya^p@aw%$LLq!z!67f?B&&6O7gtI0hyFY^cuku>Iu$lh*6rF0#uE*%$O1j ztX_3!s^B46N5qaC&2V$1^jdU}W(aspkCLtAcQxBC-{|rOKz>+2y$0-4189a%afB26 zQq157D~73a?e)|an0W}z9HDzR85qmt*%D{|Cmj*^O-(8Z186#l-6-hS;Ra&OmYol(3 z8v*nhtIl9e`;=%@A(_4pqIb%yqTRUzfi(gSsJ#P`UjG~7?0LWYztnGI;N}r&t=i{^ zj>Xyr8wah{M-dT(Bu}{9yN!EDA7Gbg#4ew-8NG$y7}Y(Ft1wgPw|OR8_aQF}7_a;u zTW>S7+5Yo?!3*s_)k-!5(t=IXX!!xe29z`z*r#MFAZ4GE_v5d>Z@`^=ZL1w$W-hvj zSC-=!%X31(Xw3JdxGB_UtFl?G=C!;YNO`f*5xW<2ZV1-R@q>WuKR7l~;KsXXaz^`i zELri#=6<84zzzf|I_oB&^s`V53+GR4Yf(53 z6(9`9+(&U`Ejb6;LiVG^@&$u_t9tc9diF7`9#Rs?`~lg;5<|+Q8ntFzdd|xv6jf!94-L71L7oiFUw2Gj!dbF0_$|ZfFL&%hEftCfpZ_agOIWJM+ zsWz}s4yILMWr1(3fD(4l(N`;!Q4++AW7PZ!EB(pf$1K*!CI)Nt$v;8V4q1PVwRs65 z!}nr?)`!R~9GLSsBmVynjVWsbdm}cjxTTj@$aC?PEHu zv4@QN+o6r3z`4q^3dhC`d@EAv0=-4u@)RlUMrZOh^k)vxkN0&#M@MKW8k^lDLSj({ zBYaYH(Hf!5@97p*Y)RdYp}i{Wpia;KS{vzhVOh@k!K`gkpvOilqY7DQZfS{D&6}o^ z#xtH!Y}bNLLu4=-1Cc60JK8&0trai9O!xe@BxcHg>IPQsX|)v(PlwT^b>nB0hJt*i zkU04D`}ul?p4WRTOb0qN0@LfM8Ho)KB54h1AIh?_Jx-dv&nEHbIkubtU1r*iEOJXc z*;$Y_EM!~zCXrIxan2#yL(l2-iw~T$h~P7Dx16=5Lnc{2sIJZ(ihB~|uaX1D4DzMxnsiYi zA?sOCD-Hb-8zqGK(fnggivhs>(R0jI0flM6RGE7r`tVn59661Ql zO(P1!y5yGZy79gZmo;n*>rcl$Ro-w`J^$2}Ng{Bg3O_}qijo0&OB_On@5Mrlx1^ke zqD0h*r^xN|ReAXf1QTR{0m`6%qlW%O`}4Hm!9g$`CtIT$Vc2@+y)G>x?s|mT-ET)e zTSUe~u(Zm9UMO=)5p=VlDTdGL-oylX~^{*h|=YU4eyI*SSQ2ZW}HhRPi(Ldf{1ABT5VhaTC=?IwH(rE)kN z=5WbDp`LdOO)8KbFhBDl&e@TVHw2LIqLryWwfSCoO1F4TzK#m04uPj|TqvBs{=rf` zm}a%qrn=@;S;_yJUr12N1no|nYL*he1M9cmY5_zm6*cHY&yoy6=6?F&e8ILF_v5TK ziwG@M>e}!DiYsDt9hC!?6d%lbS?acK$eYSdRnUv$W>SY++fwh* zhNOOLy12}j>hH)U`5zk&qbYT1(h(@aZ=&A`ppmgDtNMnJCXvKtO&=BOjnxh0uYdj-1S9yuMC+Kr|fDjg{EF5FeeNxmg+>RJZ|#OxF3$`rnsAHRY4 z)TCWrnkW|inCEBfva}reMngo^0BOr|mvuC<+1Q~)htTn{xGa@Nq%1RS|8|3CYItH{U~OVmQ_Ei!`Ld0k8B@JvTYs z_)DBb8NeXxDHJ5e+uqPO|Mazk@qUk&Wh zCgHooR1kt>SG6quIFIGsI1=I7LLH2I>>cekdYNK+l|NxUK3pyA*A*0!f9y;X`lY;;M$y=03K<(f^h_g3cN zAOYDN(AiqpEPf0GB6idoq=W^uCwcUM--M*2!CiA?=*?ZiIKGUiC~uZobUoE6_`L?c zPcB1$>Ic!WAtW}qeIqBOg~1&tvX{MBEO-W&SWdrw0Kc>HN)zwHzrbUp>fzD%C)p)Bh>lr*19A{@*WPg43rUGXYl9KYeP#RW;HRVK^3vT)<*xvy zqcNAm$rHP+XIxj_3Fs?HjD7hX>Y<#y^So2OIjvws4MCN((DBQPquDpSln3XEFOPPW zfUX{;)&3H^Evc#_cE$la|AH7>$k5Z>%_WlJ@Zo2Bbu@8bPeWwwY+|qlY==pK z(?ng8tkwPCV}GH5Ba*b*x3TG-cseK@d@FPeEuB=;`jzwxQ%HHH0+mmHs)3Q4F=c70cPOJAur}d(1@h}9} z>Al^~i>m)Qc((R+OMmEt>i!(mB3gNuQ1d|agGPGsTVv?=ejrjjjOjxS3P@DNpv3?V zaF`w`^@b&VIBau#WkXJbJ6^2EKSEVIXV9*!U2!$YcO}2aBPJnQS&X3E5gUg{WeHV{ zSNvrEx_>97mpaAH$2KE0OE{1w4yV!G0HKFX%kNdgOTpX!RkKB+ECalAyp`pLfCG?WJ`DJ01QlA~LfS4u}I@x|Pv zFJfib5#W!(prHk8R`?;i7CqWBfCncjw#0`;EU}w(YgqTg6laYQCySD%!>qQ1_1}C! zEG_S&W0+wq)Me6wz2WeeTOK9B1ckRTaE8>yr-URrE5CORpA67Iv8^|3_Z7EdWXDuA z?58oPsfZSC$IrtnSe?odXgQj!E(BXc=C!XgMS0>A?3nj3E{qgud|g3`r9=Mh8D-bN z`5(S>%#n&;978iZD#w;o(cRb`^lGWs^Yxh1?zqQnKCQ)C*?qq)*Ad9xR;wOJ(#=x) zB4c7Z9Jz6!Z8|uFs5Ezl&`YT|Y*d4!GOK?PX;hA<=YdY~oeF2g`rl>dUgZy4QyPO; zH-Zlnh&9iUAD-lzr^m=BNe~X}8cH;wY>?fjU*Po<27z?0}d(NWbG$zv6XQj2K+NR9yEIe!Hw9=vzN^__r?Qj}^4pw#xygM{ptF-(_1 zU}Yj+f9I2QgBBe#{!u+1Tnk}}DnO0m0siKGXIpJ1CWuiBNnS}&conH6mMG>>-(UcQvVpk<0MU@Z z*_jnd4?;8l&Ah}>pFjY293p>B5$QY?u_C2g;1?mW{ZQI2=#+9N#%F!4#H5i=zS`SQ zGLc1k#kgwXB3bgoE4}C8mbhSEA!|Ij=jU_omQ1rXA)o6(!(r398M9@yrX#b5G$~7_ zmFXZxRAEOPh-L7wx5w{O?Qb1f@5amk@piz)?5;gS@f1#u^d_6@n*%t*umtb8Aih(+vHPXrw8}=z7IUaL+Lw~r3y1;aC}U% z+a%`ZNc+8N3_-yLNi`CDreb@HVkprT?p6-C9`=vGm*nO?_xG=V!O$Ueos8VPVe%hm zKH_E7wj!JyM}(v9>DPcSRU{y%XOCRQj#4)9P{-GAQNgF(T4G_9X|-IYnoFGM)b$u# zv4s@3TB}kmvY(K5g`8tFAa>~@Psqx##t9^h_bp%VMk}vSxH~yqvxgJZB5 z_{}8}*}1mS@i5>3xmsZ*N`fxQva1C^nD@f+~I0kiTs~|ZP8^|q`4mLA|e;(+O0XfGQen#t}#qV6k zd}@R(`z%N^GkY&*y3UCwkFWKiD*st}e0PRIi9He;`o^07F8M1{O^VD{`&pIz|4X4_ z%|2AAB$AHG%`b*~^Ti^>(dP{V%-QmX^)=0tCtX?fK)TI-_KxPLm(b^?{>Gbj0X3d5s<45I2TFjGPn@8V~mgLI3^% zb&jSWqkDoQwD9B8t+F=l;NR>k9Q_Idtz1tZOirwH{|$a=2Dv8Gs)V-)0DT zTg0%P)fBFhPn?(n1ch(4xMk7k6TOx^#()M}?}u!vgHalq#srD3U(b zwaV;|!KCuepq!i696*H9{*@tFfErmk3q^b~Wf=+~JU;DN1s(pqL^>kaLfFMCCQGTZ zZCQ!ZQrJG4Pl5?_< z|9;0YT2;qAKt#X<#^$hwDPmv$6n$~XWdEldO8#RrgR7S)d`?XUo0uDZ8aw+G?viZr z*I?w@XDQdisk>^SWY=Gg4(_8Iw^(x9F4*nf@}H)bwYV$S;N70lvf|GY=tby@`Cm-^ zY5it~<3F}Ko#U`?M6_|B?_y-d1#j&Q#s#p+S(vlQ<829*;u2 zn3X}WVF{hY>EJ(MmU=Wfv8y6Fgc%Q9igC6uEo=3W9=>K|R1GFf@<$NSZW&9}pb-H4 zf=zHz!i>hU6YAMwu_&^R{S{SG! zufGsZZga9C%P|-`2x;TC+MG^i>WcX!c!n2#WjES39qspJgB;|aihy2a7~&erFEqIC zWkYY8A@u|vj)O6c=x~_i`#i?kLsgh!8zC@Sy3vMmjcp&Me!09UVuuu(in^D&5zDNg zjViz!)jnaNE&kTlt@uE3akpxAq@zsYEU6|ey#poEy$3L zMz;|nYUX>PH;`#!O8WsiLR0>Vl68{t1`-|?Xp?uKIDAc!=y}F;GjgDT^iVANds^=d zLoM0XVP1^Fkb@FrPY_bUoP44(T`KfN`lFA|#gFqC3L{n9zIJn%=DsTh>?Byk&h$LpK+?ccFUfA3^^H#wmoC`0 z7vrjXJ$Z>VAgU${3b(e-%^+$)0VUQ zuJ=jkW38_i9NbRoJ)RdNH|!reA1q36{WLy&;b=ZFNOMSqeDxO)Fgyeh6yj0ZZ>+}y zBd0;r=nGdGka^zmD(Gs80kepO3Vw#yM;Lw0catf!&(9RhA#SWO7)j?G8rErnC=N<3wYV>HHL#-_`?vox{Tve&0@&s_7Kt z#CSpiyu4oc&7;e+#)N0fA*Zpt;$k#uQ|AjIEWa6>o*^e)o+v8j^i~&ZHxV}JD!}NQ zf{}=pHH<9l?>PA-WtE@*de$@xy8khceLT(&qz(p2Q2Wm#XH{%D3-94vnJ$$fTEwX( z!{Xi2nJ#kTsDtFC$G96%U<97&+88FC>z4XhLUSyz1TWz@l#Ey*>~e!1M_YUD#52OP z*#BgNwXp`B8k}=)F65j-k&ITqO_Inyj*j-$z~Xxq8O9yJO@pf)v`%sS>>Fk9BCX4SpRXQ`x3!0nf0nCi^^>BK;oW$G6L zxmxo$)?yJ&Iollb=JhpXK)m6C8b1ligf-Z55b|y+X=lT|nDrmC^JSEfCx9j^>}%aG z?Uc^U8q@WC;w&}B*+Cli6`_9?n+P|1nUjYtV_wbYR3U`A9*`8P_3r#p`^$Kzwn&TW!N z>WX=+pjnIvo+%m}F*7Yn-ska7G1HQMuW zO;DFRFqbGQm67wxXxQrka@TifkpjI=F?i5D3{U?WV<|<3aFM26it6CuCHVQVzV^)yW-?9z zj^vKP4b!ZncB-C#Ts>c|_nWTQRBt(9#rI1l%nz}_&9~mPoSZFT(U^^Vr!|3xd85UU zEtc9&%JPWCbOJ*M1Y4#ay`SYR7PIZw4qrROabrfX^j&-M1Dh)-doWk!uQ6-oi_~-B zf6>8G^^9I_WcdliBvqiSxFI^1a!#`wfat3W*SXG`XW;47sxzO#W$(yW95kX?9|PyJ7!#|h7%zNH7rw62~>f2w|_fEq>` z50uP{xVvo1tANNSRmlm-mW+i7ZzQ%>>r2gn3RGGWPZO_r0I8^enUxMRg5*>;P7tiY z=^ggZ*R@pd08Ou`A^odPaGL0U;-^bdxLP|E0?k@^q zv5ldgoXwj0Yqw{p034yq{94)Qy zLvx6$GU_(V84qN;c(G&^a~M=)*EYR=gQf{E)AcO@c&(hD#Tr!`j5npP48SM(CN#9Cp3J!-~%rT*z%e{kC))$R_!4M8^9 z0PO{!*+vC$=+8~07wj^%?F)*PY8`=IdwDr_`CeG2n|4v1D$%7A?RAU0aTGp6zmfxj zPan?J<)qO_L}fbJes{Sj4C4=&MVwHw(3iTEQe&iDq_C9&W9^jVZ^b@AAQ)(zDl_w3 zpTIEIxwt!PDAnG@KghWQ0l~l1iNx)kDBgNW5v2K!#%3-jDhR53hk#T4WunGD$*b~7 z$nTrXu97I)VTxxK+fbn++?4RgUfYcLbifmFi|_e7lBX7URvF8~RJ^GJ2!@J12b!ZvaQJHIGCSMm z>XU8CuC(c`x#T6$cT5jlIM|2P8E~_L?YGFH0g{&lW4LgPyP0g z2q+~!uJ*H(KaJ>(>6|=t)y-cpH~PsColcO5y@d;7RT7kCCkL<=UZ+3gq?Gc#G||}d z4{h{1S78Bts@TSvNBhBccA8p+iX-FA_X<~z)7FXY$bBQ&%Ma5sBfI}AoWVesw5O+gVgD=djb zwNBe^(h#H{kJttA?ri;v^@lGZL797!wFG_dVG&axEU~u!f%FOr__)-}r$+hQ`2J8< ziRddaOey5>_>y4V?-Lk*c4<9i3+BHRAj^&^B>Fn$)E9NfFaRtO!F%{gbzR)f>O#M1 z>YaWe-U%lJ)cmIgQGZ^N8=mA_vheQOpUXLJ_%_KOqiC5b(m~$)gRMDrZja%f_sdd8 ztE(|qvul^Z20K`%koI+RLJ=aY2@3AVRtgswhHN8IWVFW9Y*M}d`QyK6LGdPo{Byu8 zBC5~&^c0WoY80tz?hwKmU+6sazjKrUAFa1v)`9<1XTg1LW{mm%eR_ zso|o7`Y+Fx2mQO;6(s%ZTgw-+3;DHhYt**f4?;`ZaWJ~hd`AA&4kA;SbG4lvjQr2H+MaT z!Cz>*LjTa5&y3Yz!o6YdWA*=Ph`ew!Y$_sslM}qiA-v&Y);s_m*X7nXaPFqqCM#Tv zyr|!E&Fd?urI~veTD_2@;5X4#Q}*6B*tR#)OU!l(mG+3Zi}%KOBV-(aPKhhs4Jag+ z?iN7|aFbvP@RzZZWSrV8L7{E;rJOxWomTW$aqVl}es}nIk(_r43UL9?ZOWaaB^V;K zdxBqieL2p!2%6*h^b}%!gn=Ic=E&RQEb`dbVG#<1=n#%JT0Z`wMHngsHpCD0riRf% zl&}cG(+oW|+m!HR$H$Ix@3pFfAhMbuh>0Max-eh~1PB|x=xm9GN=37;g?QOou{6{nM zr8g*nvzwbBXVg5{8V#t|eQnusEE@KAhnUbuhqw*r&5td2#DV-UbUeYvGb(G+d!xTz z$86jx6~mIIjGWspngkgKg)6x5B@wZBvE%>+Y_Uh~C?AK2lkSMk_m;k!%ef2c1FSTD zIB`d(N2{`^pptB)dLHUp$H+1c#&-l2hu_&<%H7W1P=h#gD~9=WQA{&$s(17SEn;Zi z^)>yJsR@p!bzcF$I}Nj}(9uje_YDovike*9k&@MdvX8g<&mZS(!(;;u=7cknE2i?u zg2<^Y^TsZ2Kki){(;NqI;x7B(LN`D7qn&VOy1bK&Tp5iDoGvd|2@2&FP3P~;FN=cf zoo`>oEV>tGUn$He97=taxkOmR?lN=ktj6Zt+e~`C+w3_MVO{}0mE-p5S(ew<+9cCv zO3T?d@b%z6wI373*N%3rd$A$7r(wS zc4gyX*18QWK{RUZ+qIH1bJYX@>b@jU0SY+UJgYS;kJftR!UYW|_?f^UvlEp=jZ9t; zl4u53TqLo^PC`nG^}+5f7b3E;2yB}gbkYOpCaao(gkh1~r88y74gWcK050*}qQ+&R zdiAV}iAM&hWxeH{{ zp+bY1<9MVQ$%n|IAr#VyfA&9nWQU-9f9MG9crR`nKXA{WR%eDaJ0u2dFP_*g=`o-6@PGN_L4DuP6-8CTYGwB<7_A0|nNp&{y{;vvqP{OAE z`hrZQ{S?J=v1;!Ul9W+ILhkb)PfsX#E{A81Y7Sd$IL|~Mb=H0?r=P^yHu&(nWoIrl z8|?}`qEAqlI}afG4I-Kw86MmAQ#v}nF$*ZlfDT^d}3>z zNIWl9`?RpoD#(Mg?E@m!I|0 zZx+wLcA3}!0t4cH*M0Lgve*>`dS>kE`B_?5~yP0 z_5}Gl0kgBXrn-6yTV6x!wQXIZ4|7m3ZO~9cd-BwqqBjL`kq<8i4qIFsWxI)X62Zip zhmW8SzH+{Km_FhT)M1(W@aOzVs=R*5O21vU#d^d56!>Z6X0IXgXZvAVjQkCKYDw$B z04VJ%Q3+8d*!j*k3(G!muA+$JlET8lt(iw?E`Qac*>~=F9(vlAN6H%^YLE9SaKs%> zg8sP0V$Q<>AS98=(|g|HX7h!X*0vYW;1(TkIWeHV=1wRoY^PP(CxkIk8j3SZj78_p zK)iN$iIIvk4=lRKLPStIp#<7YoxqO+NIseTj>Eq)t5;^t)t7r$bk;_P&Tg_b5IfG1kHg=Wkn;s8 z^Y5+2D98Mwhi)r5j@#(APp{Q^+}~t(LY{rl)dDcw-KnD@EWcwgjQNK)VceBDx^>;X zH!nDElnf@C;-3i_%wq=GC9<9DL=_E7pR5TAsb0~9SfKff5vUz}uB5`bx2#^#VgQMkRxB-+Xx1)0kR^@py&7R4Ln&<@5py_rt(;-4*Z!YJ1ko|j z4GC65r^5NkwhC8r=mzm``ufb;DW=9k;~BHlH8W~>rxmAzacyqr))pf@x%o6w#}8JY zY$jq?A-@vh4sgAB<*QKZ*}c??j$}^jdq%fb1*?d}|6*^Z`}5W`uC6?IV<9^S9jONr zBRe$0$dsOQ);FQny$dn(2|$w^%9Qf=tB8EMGPR#nckp`9_LJ7L8eJo93Kv&^J-c}t zN;WAV>PE%Hz!HemK$ihHJEtq%(}%<<4u@b5GAQY;tSHR=H6=)zv#zgZYNA{8 z?Kd;mAfM44<&X85v*qF4_-U5EZ$j(QXB=$ewC2l);neD3x^eL#@2x;p!Mg3`Gd{szpwvh9Y+(8LW$`1^k;AzlUQ3CC@f~DU03bblB}dy}crCe~OqBWE;|8?yhzpb`cw#aq z**UWw83_-&&IszCwPEp)SE0Ijk%DjscMStdnnXdS!^|5hs<22Cm=G}Ak@}~cACztA zLp1akm*#Hl%ql z49~J7h&^=tEkEx-|A`!p=k}-O3S3IF*;bARLcP2>TO3KXf{ct@jFHQ^O^)d8x3*;wah$u6v zf0q^6o#zkEu{F%IP1yean;d8i%~v%*H$zoyeoh@~Js@?O_JBYUIH4eUJyMvvc`HOl zM%~euBF-H$ss^GM=&h-QbA-nc!LsCDIKu8V`hk3Hfk~#r=XD^mUD~YgziutVW=W~% zID;fsL_nmUGmr zLXEupH9s2qSdU+T5gV7N+I#&_%VO~Sglf>+fB;NXHvfDq;YgL?Fb|V#@(B0E8VqE< zd9`_^eD*&HTp%INXEE<9>}!ub$g5;M`9#~Z5XMHA|A6_~D=P+y#jDQy>KSWv(Y0-};Or%aC+ zkAJ$ecXxU0GAGdc027EXB|Dphn`LxrSWKs%WvK4|9Gp6d2UqHv1HGD6-gCN zJyu3tR%kxfFuyi84C51tJ+4ZuKay_+_Opb#}miTEV?c z)U0nCP9R%B)w+%$>KPc+en}Lcv_#C24ocPuTR^K^i5z=>4YA=~t`AuA7*AsPgOsdr zT>-I4kf=k`Tiv$klC;<-CvMs3+jl!wWW2;o8NN(=JX5Me+fhG%YA>WC2A8&twj7%$ z+z_F)ykA>>x+fdTSwUmIfz?Ut2JUy`HaGch%bb{>1p7;r4O7;)Vi&?rM}*#RxwBZN zU5b0&tDMZTXyg{mXqE`?8>iaADhuG_#Peq9bW=<}tiC3!b^Zyg| z9I^J?rCHky;Hn0=|1w|+BK%95OxZ*?fYkQwg*R^k@>@3h&)?lETIeK1!_Dc7F)bVg ztKge?=-&25`g4^xX)gJVY1^zB{RFqw&c9}84;RWS#3&;5``GmXEH%^0xg$bk-62`a z0oQG(&y{~H6Jo;M7k)5el>dQ63Yrr0LCHC5=@ zwYR@ZxWDQqXT@tDXMfCC-~FI9p%6=2sS?f2wb>wWS8}_8kgJ41Lu3K$@am!0)~K0# zL%csa$imOp4aiSYv=18c3ojeg`yx)st+rDKdoxSa3%!J1fqXTUluS zcC}_z6m+e4PRV8mDhr@&WwodC;muBkeq@(9&Ifeguq{+=`&23ZoKXGJlEzpNUTu@y`J}JK4OF?WsTP_9UOGn?nu9`TdXq0T^ET7T9 zdtFF5FOP{r?p~ROeS^kR-agkz)TDtM7M45gW{-ywf$+P8SwX;s##bOu9j&60(< zG)PP9a})^2ADPQC4QBMz~d((f*NJNp3jA}jj zy#V5f+^5O-Nmo~D6LGMMvSse#*vwx9g5?M-C8HUcgtxYgi#8BI{q74fHk2hExF4~W z-wuMD24%w88u6h`JH?edd@4Ev??dsH;rqJ*pWEMMG~|?!R!*UoF~A09ZrGzWJ`O5Wj)1&U8*;X#KF~3h`*6 z-Lhn?WU}mbb-u%vzU8UasKe1#EGW73T;fXtpu4(A2~%1zjbo81R%~V_|@4Wg>iqIzK%?{Ys`p zB;xqJg95`>v0wbMO;>kApGw4R}hpLJ_nc(cFt>td5!0ehzvwCF~yFKLXGW@t9&-fG2-LQj1F1+ayT~GaOIET4;T@ z-h;1@6fHv|S^LQFtx2 zha8{Pc^fEub>~#qnhC$5^#LW@D|JWRVQJ~}5O5`I%;nTgZ}}|cfxFsD>F43jDxW=y zxerFKY=C#v)eL^a2s` z14;FR@n)!r@21{2n~qOKI2=cgK~ld?3Jw`_tx!tzSCJhMlujkr=5}974GITRrfa$o zYmcKluT{yxXEW*-R?}-DM@b49P0ws;Kke%61ecy7|lCP@3l=ijyx}jgJiA7!Mh#&6cC;ywK`g>oXxDvNC!fz3jQqhghhbb)`czS zjCLyKk#DVm;XGYj&%)uca+vI9baaxot9Ml~xET#Wzf~d5!d(?{F{Gy9SwL@zr>W`< z3FpF~4Cw9eX8wKU%$=nqn5mZ;*PWadfE793S1xuA#w|!msr6#+g!!|QTn-d`au}UD zD-XqZ#Ip*h77;j>dvSO7$6uzurwM1+>WHgr z#Vh_11}Y68|J(^a;`a5snC`C1MPuh^s;Ty8VaGtzk=vt4_16pjdR14h{4rBs<~@gs z0inIVpzh}kCZz>;WnbmrFx+!*V=~%*xf(G|szy}#A+)cZHS^H>+b7?@`hZNBah2TC z>Igr(>^O4L`ABww62BN>bDlxtki((xCW}ae0(Eq^RkdF3zlwLJDvowSPv~37DFYnm zzs|PGhbi0mO}oCgavDSN3TFf3flh(-rmYye7mdQm&OeTZsoBYiG2xsA(-W@65txUg z<{~V2ps-A4a!M!IumXFNYgqVMVB4qExmN!t@q#8>-<2~$@^qL5!TAWYg^0g;I{S5d z!F`Pa0_X8h1kq&ha2JPa;xYZEIr4x$An0wEvD(BmH!d5Ab|?C8-JKA8q*w1b6o|qw z>hS31A+A!R++Kcd!X+d7Tl*~Ur7l5s3e4?D$;o$yT3=9a?Ai~O`5r|tO2X)KX&*2JR=4- zQ~^>U)gh^DX)?^iZWR4^>Uxdni9Tlz<3$`F|2k4#k{`S;vsTzLk%qP>Z7T0TzXoX z4z%J9Qv%yKXIxSRcC`aBOJ!am4zC#_qMUg!4m1h3;%_E0+`d{7dh5KKppLoUpE~wA z=JZqy==|PQs*m*hV;kcY_N9dC`y3U@L#!Zr--%*TFW8Vf2x~T)@N}mqvzhNN^3cZ8 zO1}WS%jn-l&gFnVWQrfC@~@G-L}hW8JVXAAIcz=2^p*Re4t&=~as}RtylTzo<}4>2 z%dUA&5l*?XF>qIymKq4vgJ1~NZU)x;DugrQ&cBtc);KUbNlL~fOtKm65XV#eL*gc9x}Iqg~+X_H{}NtAJFy~3>UG+=C8JIm<>bD`rqBnuKjq- zCUX^1^zR^b?+^*ByMSl2r5+$Ut^Sy@eD8vQu2Z}?;ox^B@EOAr@|R@^yze>gIlUDl ztZlb1=z=C<1{DHF>9Ymc_AgZBBE-SxsXalOnt995b8iObua8-8Ic@)S8{>JXPWU@#%K+V)~%1 zh(db*r$XCA2rY)wWhjS~gxQnTPGbPs1)Xd6Aru*8Az0bDS2Odtm9+IT zRhIQlz(I%Eqo18t_%Fb6M7uOh_}s9a$^uL4-(5hyLD80f1Jab3n>vv775aJKQc?-9 zI3}F~wCdT*XGVK1zx;t=XOBUt7g!{9CPc9^#X;Z`N-1*c z>M>0I{wt04m`@X8RVG`W3U&?&q3-2~C|Aig4kv-6=F@u@U~js^q$OzKh(H`y8Y*Nb zbhX=bU3o3Fn4FUv6UO)>J%)5D+Zz2vRs4B_lz`O=YF1!IWgRsSb~~!(^{JN=zAV(L zDM7m{#)f0{RkWfkOmA<*)AOt`i&odDuZO|1P9WZb1xbVl$nCS}Py)$kFp$zY)8rU( zSP<6nO$Rk`j^j_)Z(AZ~U$%*88kc48%zn_Jt?s9%V^~I{A*S}-xW{6DVmGI>--}%x zV=hD{>6^uqV%jA%w3V4dA|b%FuL%Jjk4JRYPUg^h{80jKC`R+an>!*2v_k5ywRYgZ z5U-yd=UFU?)uEy}6-tt^3oMTefKk|rIVrBBd0J;*wsYM+JbK*Zj||9NixQeOL`P#? z-At5nkJT}$r29FI=-Cay4!xG|7I5y9x$4GwD(cI|$-X5tC-+o3!k zUn4)l9Bf_N!fa8j>3e$DoM};q3ndN{9Cf|Nz1+IbV)RtGXIP;$q*Bun?axn`B8&d0 zwJ>bYtIln+ilIi`#FFB#j(0yb`j5Fowk(G|Ehe2?fnuaOl>8a2DCq}PZmGPZxXdnO zBr9xywgt<|VJ$U_Y$qu?sXWd9+=h$;L3m|isFWK)#B=HV?;II3@KJp9-=X(-VMuMr6~R7|?tqw@?6#UA?BWb@uSR ztiN0*HVoO1bxR0zY^3s6<`QAd_(n(|!@B+-yskR?zvqhIgiybMkX@Q_e)mlu7#RT^ z!Gq+ogPqTFg|;-p^o%zA!DZ;pwG0N!_9YWkLOdyXm*46l;fMX#Pp`mlm6z&LwB?c3-^v4fV!L&$TuC!a(r!v^@LA;XA*9tv5Hy z$W?i7;K3CQ`+_jfe3Uq%uX~|SV{-t6(P9R%itR<&q+F zn~ZKf?N)ZOHWfT8AzR@1GS4D?@_LH7jw8EIlu78oS7Ul~8m$(vDZv};XEWEm^R4M< z1XW*za+>oCqXi`Nl{@uHZ)CIlDnAEvwLVc$u}WZ&GKPT?rR*CX3Dwi1&<0iiyCGPU zZi>ZgB%~aI{c-Qah|4qH`U9PmBWRM0iDK2vKisqb7fbEEwf+JnVF>aC$W z7ZK#^9jlcL5rzeIzxn~j%NZcLC6Zv;rz(*vonw14zx;)Maup<@njiRq{2K}>Bl^?d zvlqw#-N6ZDYPY#hxo%5<=sl`5zuxf-1lq?!!h3I-`{aezIA@mHf{&833@FHlMqr$f zjnOkwG2qD95eV-sqtYeOvx7)rPf)K?lJ8e$gC$S&L&PP?DnOmXn=Niy_^KPq81@>x zOYVsmV5&!^Syt%4h>}f(s1DCN*WYv00xj}Yf7)4AFZmDqk7c-{ONIjML!WjKpeR(- zslKvkl6$wf@`AI&-BT%I#jIrcx2n)uErGj_h`tCGq?>;9NoU)y z#py}PpAU^#Rvi)$y!J4~btPcS_3XruF@v*qwY@L{hS`OEhoSl8fjdZ3a9WU7QiN_O z87em+t9qZMxrcpgUc2%h*MPTXSGrILf3#-T=9#&R_Y{FM*8b3}26mO1&)p50Q{>5)YSp;(kA>lh0L_HZ>9wjZ;wdBEz_LMyS`O4ber2 z3pitg+tJ29BOYU?a_nvPh4w~z-Kf>{tJ51qc*viN zyk{mku-|n2+ovIKXfySH3iUwO`CeL70@K{co^jexrjZOqv#D_!064&Y-3BPnZF~L* zUsruR$i9qjh*%4e`Z}zgLqAQ-7jrzLQdACKhkB_Lh&Fj`=OEw$@|LC;ICy2rE+h+~h8%?wv4W^;@ljIR(eG8ljQX}B{OOiRh#Kd^} z4~KFeGU!yg&D@x>$-=Bp&hS>Asq9tn$+a*A-!o+4UFgOVK$`SBo@}uZA2-l-(>_m! zIIT#>Z?PJZV^>LsY&h52Y%o$Q-LMWKF*HOd7JMduxVg1HAvbZS0NHWwCRWj<4~{EO{X3fop?K`kc?V}F z)>ETO(?-i#VL6Ta%S^(4&yC=@up$0{Hx@;TD%z_Rk#BYWWq4d=+K?%qv}UYC`;J<- z&^di4ll5N$jEY{8U!-t%+qzcS= ztyH*T^dYVD5&0^!#Soqx7d>7SGv4T!c58ki(c+)2Anw3ZP=;(ArR`=o0x`pY$SzIu zv*@IQ0et}{bZ8tUlHf^@HH98h5+ z`Yari2|+HJKj?RCefMrG^WRW$CB7?lV#L+<=I7r48 zzO_o%3drNox_)mTaiw_i)UJ1uufF{Dz2@^^gxjJ%k50LQ6Q0R?PDoRw3-#t4luogn z)vDA|!{_Jkny_f~b*kt1=oIB9o_s$(J7JZp^6qu15c`G5~{cUFBgP+$iq@2WrN9*jfnZo67?u!L3?8P(^IWp?e^XyQJe8kThg%iwur=4J% z2+r6HW`rT6*trkQ)eTI*h7lwr_T7^^G3-R8-zc_TSKPf5utHUSf^euX9#8#(J-R^>}tI~VzDI%=A(%=hr{96MzIKLrd6T~zY^DayClrPt{-a8kJqJ=i! z9PjB4R|6#%X%fSvwuB~|v?QK%4Y02eD3T*3xa3p9^)P4@tfo%Y#XUept%ct3l4U<< z)%FaW*<8`$;pfdpe5D6xI1HxhjFh63=U>Pk2BPj50EjKe-{Iq9SZYOO4FtUaEW5x9 ztY(&EwGxkw?gP|lSdy;V zSph&}@+O!udq4#(2VQowbl=W`{-dxH_T6<{xa8r3t&=@7E^x=`+IEes?TrGD(0RF9 zZEecl?DCCP8dUB4E!%xf-)8f(W!=XvVN9#k{^qh}Zqyo@hmSweq*>hd`4-jh z=i?G797+V};AwC`gXe0uz)<}jtNTQb-XwhTN@@F^cFStuN*VMH6lLu$W|G5h|26_; zDy+|XQ)wEq2Dg+u?vxdxropS4ZHrlgHH|*U0_#vg-d38L4}#hV00_m4n{8UyD64?D ziz>r^&~y(@@_UN2lxTqnZ!V#6exZ?x$Xn3H{>~kywHu=45MVZ2`3<=_rktL_wT++g zO8aZNBOE_mMfD@bLQIgGgHC$#+Vh3^B#499Mmkq^*Uo|!{T*Ma6IcOUqzSDOjF+;f zx|M|OJJSf^pI5~v=9!?>)L5-g0^|dDOe-ecx1XEs(EI1za%g~zD;X5yCwD1ypnX#+ zaj_1Fjr~t0<0g)ZpfaZpi0FGOPx9kctJQ$w;NF+14O;imDn-m{vTA=EC)WEhR38}W zq0P$T`s>>(GdCZXoNLH?EOUG-J4Jt|wh3!3Zu=|E6UR?wvF(v~+T9dEs#=0k$;ajJ zv*-oGnhP8vohqi^8jM#EsAxFr)u=p&aR zDDnh%N!1w5vOapOSImZoB41r0&()OfM%Xe9n|fHDsb$GbN!NYWWv=sJ?h+w$Fl3jM z^)@0Ks&whCFIso!I80j#KmJgTx%KJ?HyszlRE(di`&tN{6J`bqdTgZwFs9+RAw6Hw ziLb6XHU$NEIPm)geO>i}b8F>L0pz8(3jU*xDQOjZVRdFl2>pymn}|pcDgC7JSQxS zNiug6UJ7_`o}@Y&E4L*2x3^{I96h38vskcOCZsX=d@w+HGwNkwp?(YWnl+{h05l#a z-tHyBb}UM!0I%SrQe20h0S-SZHMl0t5f6ko;dPbVKWVY^HWlJon{PW{5tB{6; zI86y!+2v=m;Mp~dS7v7@!{0DJWUp{PS4pC=^=2c=2Mp*5CLNDo+Ux&DkNhasZhmqc zpJLkW)*MH+1Z@x7r%g9LiNGpVl5!jYOqwO-I(i?;!rT1wZWPV_V>7Wh7|cX?yuFL$ zVK|f>oMm+E!iCN%=9yH$IVfo8OnH=YuH=6-fzmfAX+N5f#N|wp0(4vjE1}S$u;>-H zl{w`7PZl6P!KNlBLBhc&DJ&tEdP(9^o*MN074i2WW|7P2gk%|N=8ZJ!n+c||1RDTgQ5IRLihD&y z=4Dh6Ykrc`otdR*wsofAX7;udrjaUg@U-z)a}Rtngf4x6`C%dpN9!*xCQ&QD zL00M24MK1jvX-bc&-!bp5nuyaK-p?>e7xVAm)DnM8`5W*biV~XM zVWK9u15?lYIWP+&rd;RV57b6r)X8H<#s(g4iU3>}dbG>5;=!e0g=)hQAJeH;# zegIR6;bcqF7oe5c6b$1%b>?y7!7}g;ZNe6Du+K%);_P7%q(Vp3H^;Os9QcjASkiOw z@(|HfP1Ouf47nmbrQm4TuDoBd8^@x>Ke}|1X_x=&KvFNqCYJAGcr)IcdA!mklT!RA zX(Ha8@di_;>YuW`b}USK6i{?w)v;+Ot;`WIOI9R$1vEuK4G>>hn29O_q(x=p za(+dZMn8*nE%)XdAW&Eh8bcah4b9-zroj z#M~oNWvQc8I<&I?0OL?SP)u9}&{JD9uaeTBRM2UY^8^8@Ncg zRvQz;UhKGqdJjz1vA(MCxr$Du>@ty(99HZqbE^mdW^=AeSCs&{N8g$$G-D8-^uNZKR{3ZUmRsqbM5JQ+m#_TL% z0Nk5K3a{IEw-ebVHgRP*k4cx$1xXz#TEI^;1a zusYW;bt6NdD!%DqE)z%d2MVbJCqKaPHiq)u)qopnmER@$$Y0n3I=lr@6y5fP5y_T_ zCcX@wa|@P)Rg(AvJ*OD{!{!Nrp)ZrK=G;IRL$HY-NcM8dOFE*OGVoTGEe2*AGFbs# z(8ND+eLB|YzYlDfwPRPE2$I8o3biiUHLl)*JGQB(*5=WRuQ3%QD&>c$5M}S2msP`p z>%CE~^Tnc6x+YrYFXS#iHGvUZOvY|o4shyOo8{y@omb(FM??5E@u*!J z9R&BIog?wDk$w0e<3pvaDS3+dDQ0d}qH_1^0z7jP9z8q2_ha|u@GlVVqNH0vR=uUC z=7M?$>YTAsIL6fMZT7A>e}$PKOWjEXl(E4`lN1JDCpZIbM>dWE zSyFsKO#t%_e_i>Mhl%*IBnWddwOR{HX7ZIrsmGQZmYgRv8`L*}!kP3f?)2sX)?Ab< zA!?G*IQL!2{H6|P{+Hn-+ABIm2&^_bI!O%XgIdGAk?yp%%?Zl2$uzEs?7(xa6>I58_E%v(lZg!eWR5eh8cp*BOJBkjdIOti7q2F)xeCR26Nm zm2&+q?aXjWWi1`UO~s7&F_`X+Ij0hO%z-2>8tVttoTc{aV!!qN(aQ{W5cQ17- zvBa2L?5qjp7o=DX1W$NClBZ{9_Xt0W*|JG9MvJ#w3eji==<2OmqjwpQ9{r}JXhWHL zL4RdPeKMPlWr?Mw9U0QkhbXea9SSc4oa+iD|Fdtw21FNmcm4I&FFWzgl^(jj(t(C529n;t?$ksE!fN z<2jtGL76$|(mECO1=ZH66Zo~@Zl3os1OA_`sBu^oAp);AUBoC-csPp}RK_OyN`qbP zm3F_8rA*BV*dB1NG5m{8eV+D$LF@=ia^lzsBy$c)h+4>WT#^c7xIl+e2nec^ z*ChtwN}xN*&2k}cU-;Xe+R2+5?V>nQp(*hYs8>tCs8Td&&s#GNFZm!J!sC* z!HjQ$W|w}rTyqY3TQp%t4-Noo{JS^BgV*s&$qV`9tBy_nz$W&Wp@Tk2RNAUqXa%u(}HNN1j`itf1LS&}mH$sY(L)L*pbAW%&Pp)8vFoO8sUG!ZZ}?36nQNN>x@lO94*-ydyC96s?Rho& z=AY=znFq^aV}b|W-UH|AiwWI(&J25rPdSKqA(n0nZlNa*$cvCMURFYfp=T2a|Bz7g zhZ01|+r^yENrRtOoo=~VKpc0idCc(@a(sikZQ*O&e7SB~7RjA>Uq}6NZ<^GwL2^PR zs#ir^aZOxio{H)8s?cjr;heSm>z-Ewd;E+SJybk!9mt0qO<^2z@0&J)qs;yUD%BAm-T7?ev|c_w}#khtQ%P3vTty(d_tVM?`x1l%H*y_^X=iuMD0jGX!=i&y8kj8ei?7HX(UO|=G;ivZdQ5bxIR|lw$ z$SsKZsi!(%;2o>XJ(nojC6G)Rx&7Jakv^_qXV%c(?#VAl$%DkDy>jUV?ntUl8xk2x z4yX?^$pMY$q1u?C5hdqbLFGC%^= z2Y@ejg9UUNaa0agj53M9@x-Ih+dLhXKu0bwuju;y!&6xCTQh>vPBV^ZpH#(_?|1R0 z%zj44`ES+BGGzll{ntr6L}ByocWiaBwYjO26BtK$POrPZcmQ`Y$VCbJV$fO=eV$`n z-+ca!ldUdMf;|=!-{CtPeXcpH@eyJ*g|Fa2ZE`=KJl4V&axoI;3HR*be=tdHAuG#Ue2OLFL4(G4BsL zp4oZ7RJ*;D-YQmwAWN&npLrdT38_Z#5waHz@Sjg3cc^(J>qY9gqs4Xd8J^upjzIZd~?n;RT9$nkA5}9?QT?q^$Y?Uluye$Zz)<{R^ZZY+Qr(#hS6Xr*Y1F#Mr5$c)*( z#Y9>7KM#)p1y!Nq!DU(r)t_{Zk8-tw7+w zm1Yj2;NHO$msy^SocY|IU58JMa2x?YA2a!@N@~KGMhZRpDem8pqw`4}xK_Rl_i>$^ z51bUxf6($CP^=3$nay@e&As=i+D|lkf;F8yI~YkVatJ6xbDB=@jza`7j_=v=VRQ_;$uu2#jGmy^ zY*ew05`A%F92#cAZ&r?7D`H{4k2fJJO0vREId5H4{e%SU61{<}5Z8BUTv|7ktqXYQ z&z;rnACIZa%bqG|6Z41_yR01~S&YH+ZnIqDT902tw-%%(EWATPEWO5_;w9Qd)D0g> ze@Up3h|2q&nHYH9`$oPHfdI;+k&KB!fuH_)=ZWm8nRW_OKa8rgGE&D@vjj_DBhc)B z3nYJz)`Wgbgzu8?UKK#BPi$P~t^1`OgjbKCMN?1Q0l`h;KsC7=f#JNK-_9L6T0Ql? zZDGM;PW6fMRUs20sas4?k>>GgYwcK2u@B7NTiNB@WBwF(PFaxCq~M&~uM#_mF_oU3 zC)5YZRK2=Os9S@;%F4upCd>x5^KY6C4gL` z8VROPwCOPat1u4UW?}7tawB-W8LQaF1*w zYx|{Q{c);u3fNQLFX+v2)VF+3s1<+%l#&2RdZ zxOuQha`s9j-EEgx^Cud{Z;(jQ6!~drw=Y@IXJvPT7)Q2dGN)mUwz>UvF9}#=c#PHkm;B8v>I>Y8S%h zs0(bzUZtX3Q_5>h4NRS(493&XNc>ZA#UO$ZIdP*r+yhv68}Ce1Toejn7e>aymiJ!h zUI9ShZ;O|^68UGz* zdQdPiq9;a@>aIk|nLLF+G7T=Uq#3gR>Kof`N~8hg%%$Pn>m= zKI3=&UK?X5X&jT_7<0k0M|94lF$wb^PMX_$1$$v)kKVwY zQJTvgfZP#Hx)j&B$EdVE*zWCDRa*WBjR3L44lc@h2RMOOW~Gs=dFR0{nenZEsOX#I zSA0VT?cHEZGv)(s~w}bg#8vL$+wTPhD3Vl0v0U3d_1%}DXeZU+F(Gi>Rx&OSKZXd(F76TVSiwn~w@XoU%`%ctE z^#=f|-9hq302G_S=0WV_@<3)J8-!{2({^xkXW#C1}j2;_Zr) zh6hiO$P*I5(RCl^%k$kaCe1AiXLCs8k{c<00H#=TQ~=bZxFgM-`=O%&c6TLZZ|cbN z(5txPL_zzdv9fPP^ifX=^M(5bfxv6wUV~CYA9B}m8^;6RckxJL;`2869ZT)V5zppT zDybOCHLyvBs{%KPU-YF?eR>VlQT_ zvLYesG+Iq3AduBr<3XUSbnnukY1HY`LL}_V*%@mYf#Lr&{kpPjA0|IEThJ|zh9=s& z&8O9Dc4Qx4hh2lbF#+3gBWo8p98G|=I)k|@qQae1f)%UEcs&=S5{|I|G6=)KFM4(` z;2wFZ=Wi-QT5j}zxUbV>Rz>JdNSS(yFlRs47T5Fv1Y_6Fh7K;E{G-kqBwoG1$UB)`v=t6UwI8Ah;H=K&KP?30LUu<0;=W8*i-?rI( z4FyXKGv8^iIk6u4mA3Sppu_60-@C)%LbzWsiVQUV=5^kymm5u8$P zARbwf=@m#Oe?ff{uXJVI1eW0ro=9+kZpMAM7nGQ*^xRD{{_=j#5aU j*9yR33A%lM#UE*NO+M&41K2qK5}*J80000000000;wIqG literal 33090 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 Date: Sun, 23 Apr 2023 11:11:58 +0800 Subject: [PATCH 004/237] chore: add quota overflow banner --- src/components/ConversationView/index.tsx | 4 ++- src/components/QuotaOverflowBanner.tsx | 41 +++++++++++++++++++++++ src/locales/en.json | 4 ++- src/locales/zh.json | 4 ++- 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 src/components/QuotaOverflowBanner.tsx diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 59dc0b69..4f79dda8 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -18,6 +18,7 @@ import MessageView from "./MessageView"; import MessageTextarea from "./MessageTextarea"; import DataStorageBanner from "../DataStorageBanner"; import ProductHuntBanner from "../ProductHuntBanner"; +import QuotaOverflowBanner from "../QuotaOverflowBanner"; // The maximum number of tokens that can be sent to the OpenAI API. // reference: https://platform.openai.com/docs/api-reference/completions/create#completions/create-max_tokens @@ -105,7 +106,7 @@ const ConversationView = () => { }, [currentConversation, connectionStore.currentConnectionCtx]); const sendMessageToCurrentConversation = async () => { - const currentConversation = conversationStore.getConversationById(conversationStore.getState().currentConversationId) + const currentConversation = conversationStore.getConversationById(conversationStore.getState().currentConversationId); if (!currentConversation) { return; } @@ -220,6 +221,7 @@ const ConversationView = () => { >

+
diff --git a/src/components/QuotaOverflowBanner.tsx b/src/components/QuotaOverflowBanner.tsx new file mode 100644 index 00000000..2ab69d41 --- /dev/null +++ b/src/components/QuotaOverflowBanner.tsx @@ -0,0 +1,41 @@ +import { useTranslation } from "react-i18next"; +import { useLocalStorage } from "react-use"; +import Icon from "./Icon"; +import SettingModal from "./SettingModal"; +import { useState } from "react"; + +interface Props { + className?: string; +} + +const QuotaOverflowBanner = (props: Props) => { + const { className } = props; + const { t } = useTranslation(); + const [hideBanner, setHideBanner] = useLocalStorage("hide-quota-overflow-banner", false); + const [showSettingModal, setShowSettingModal] = useState(false); + const show = !hideBanner; + + return ( + <> +
+
+ {t("banner.quota-overflow")}{" "} + +
+ +
+ + {showSettingModal && setShowSettingModal(false)} />} + + ); +}; + +export default QuotaOverflowBanner; diff --git a/src/locales/en.json b/src/locales/en.json index 89c58e96..2a6ceb67 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -72,6 +72,8 @@ "banner": { "data-storage": "Connection settings are stored in your local browser", "non-select-sql-warning": "The current statement may be non-SELECT SQL, which will result in a database schema or data change. Make sure you know what you are doing.", - "product-hunt": "🚀🚀🚀 We just launched on Product Hunt, please give us a vote! 🚀🚀🚀" + "product-hunt": "🚀🚀🚀 We just launched on Product Hunt, please give us a vote! 🚀🚀🚀", + "quota-overflow": "Our quota is full and we are applying for additional credits. Please wait a moment.", + "use-my-key": "Use my key" } } diff --git a/src/locales/zh.json b/src/locales/zh.json index 8ee4895e..f770b573 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -72,6 +72,8 @@ "banner": { "data-storage": "连接设置存储在您的本地浏览器中", "non-select-sql-warning": "当前语句可能是非 SELECT SQL,这将导致数据库模式或数据变化。", - "product-hunt": "🚀🚀🚀 我们刚在 Product Hunt 发布, 来给我们点个赞! 🚀🚀🚀" + "product-hunt": "🚀🚀🚀 我们刚在 Product Hunt 发布, 来给我们点个赞! 🚀🚀🚀", + "quota-overflow": "我们的 OpenAI 额度已满,正在申请额外的额度。请稍候再来。", + "use-my-key": "用我自己的 Key" } } From 93388acd233ad35004008c6d6ad3bdcdbae1f2e3 Mon Sep 17 00:00:00 2001 From: CorrectRoadH Date: Sun, 23 Apr 2023 13:47:15 +0800 Subject: [PATCH 005/237] chore: change the init value of table list in useConnectionStore (#55) --- src/store/connection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/connection.ts b/src/store/connection.ts index c900a884..a03867d5 100644 --- a/src/store/connection.ts +++ b/src/store/connection.ts @@ -72,7 +72,7 @@ export const useConnectionStore = create()( ({ connectionId: connection.id, name: dbName, - tableList: {}, + tableList: [], } as Database) ); const databaseList = uniqBy( From 06d32e7620e7d2d8c7e46885ef671f565706ee97 Mon Sep 17 00:00:00 2001 From: CorrectRoadH Date: Sun, 23 Apr 2023 14:40:35 +0800 Subject: [PATCH 006/237] feat: cache database schema (#54) * feat: cache database schema #53 * change the pos of cache table list * eslint * read database schema from connection state database * delete dev log * revert unnecessary chagne * revert unnecessary chagne * Update src/store/connection.ts Co-authored-by: boojack --------- Co-authored-by: boojack --- src/store/connection.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/store/connection.ts b/src/store/connection.ts index a03867d5..f0c60ce7 100644 --- a/src/store/connection.ts +++ b/src/store/connection.ts @@ -28,7 +28,7 @@ interface ConnectionState { createConnection: (connection: Connection) => Connection; setCurrentConnectionCtx: (connectionCtx: ConnectionContext | undefined) => void; getOrFetchDatabaseList: (connection: Connection, skipCache?: boolean) => Promise; - getOrFetchDatabaseSchema: (database: Database) => Promise; + getOrFetchDatabaseSchema: (database: Database, skipCache?: boolean) => Promise; getConnectionById: (connectionId: string) => Connection | undefined; updateConnection: (connectionId: string, connection: Partial) => void; clearConnection: (filter: (connection: Connection) => boolean) => void; @@ -85,8 +85,16 @@ export const useConnectionStore = create()( })); return databaseList.filter((database) => database.connectionId === connection.id); }, - getOrFetchDatabaseSchema: async (database: Database) => { + getOrFetchDatabaseSchema: async (database: Database, skipCache = false) => { const state = get(); + + if (!skipCache) { + const db = state.databaseList.find((db) => db.connectionId === database.connectionId && db.name === database.name) + if (db !== undefined && db?.tableList?.length != 0){ + return db.tableList + } + } + const connection = state.connectionList.find((connection) => connection.id === database.connectionId); if (!connection) { return []; @@ -99,7 +107,14 @@ export const useConnectionStore = create()( if (result.message) { throw result.message; } - return result.data; + + const fetchedTableList = result.data; + set((state) => ({ + ...state, + databaseList: state.databaseList.map((item) => (item.connectionId === database.connectionId && item.name === database.name ? { ...item, tableList: fetchedTableList } : item)) + })); + + return fetchedTableList; }, getConnectionById: (connectionId: string) => { return get().connectionList.find((connection) => connection.id === connectionId); From f8f089238a4995c7beca8c023e2fe1b4642de588 Mon Sep 17 00:00:00 2001 From: steven Date: Sun, 23 Apr 2023 14:50:03 +0800 Subject: [PATCH 007/237] chore: update table list check --- src/store/connection.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/store/connection.ts b/src/store/connection.ts index f0c60ce7..ff9721f6 100644 --- a/src/store/connection.ts +++ b/src/store/connection.ts @@ -89,9 +89,9 @@ export const useConnectionStore = create()( const state = get(); if (!skipCache) { - const db = state.databaseList.find((db) => db.connectionId === database.connectionId && db.name === database.name) - if (db !== undefined && db?.tableList?.length != 0){ - return db.tableList + const db = state.databaseList.find((db) => db.connectionId === database.connectionId && db.name === database.name); + if (db !== undefined && Array.isArray(db.tableList) && db.tableList.length !== 0) { + return db.tableList; } } @@ -111,7 +111,9 @@ export const useConnectionStore = create()( const fetchedTableList = result.data; set((state) => ({ ...state, - databaseList: state.databaseList.map((item) => (item.connectionId === database.connectionId && item.name === database.name ? { ...item, tableList: fetchedTableList } : item)) + databaseList: state.databaseList.map((item) => + item.connectionId === database.connectionId && item.name === database.name ? { ...item, tableList: fetchedTableList } : item + ), })); return fetchedTableList; From aabb9a0ce5074219d7054470084dd346651613ff Mon Sep 17 00:00:00 2001 From: Tianzhou Chen Date: Mon, 24 Apr 2023 00:48:45 +0800 Subject: [PATCH 008/237] feat: add prisma stub and init schema --- .env.example | 1 + README.md | 36 ++++- package.json | 10 +- pnpm-lock.yaml | 148 ++++++++++++++++-- .../20230423161119_init/migration.sql | 33 ++++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 34 ++++ prisma/seed.ts | 70 +++++++++ 8 files changed, 322 insertions(+), 13 deletions(-) create mode 100644 prisma/migrations/20230423161119_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.ts diff --git a/.env.example b/.env.example index 0e535058..52d79336 100644 --- a/.env.example +++ b/.env.example @@ -2,3 +2,4 @@ OPENAI_API_KEY=YOUR_API_KEY # Optional. # OPENAI_API_ENDPOINT=YOUR_API_ENDPOINT +# TELEMETRY_DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=sqlchat diff --git a/README.md b/README.md index cbd94be9..8d89d6bc 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ SQL Chat is built by Next.js, it supports the following databases and will add m - Only the database schema will be sent to the OpenAI API. No table data will be sent there. +- If you use [sqlchat.ai](https://sqlchat.ai), it will record the anonymised conversations. + ## IP Whitelisting If you use [sqlchat.ai](https://sqlchat.ai) to connect to your database, you need to add 0.0.0.0 (allow all connections) @@ -64,14 +66,44 @@ docker run --name sqlchat --platform linux/amd64 --env OPENAI_API_KEY=xxx --env cp .env.example .env ``` -2. Add your [API key](https://platform.openai.com/account/api-keys) and OpenAI API Endpoint(optional) to the newly created `.env` file; +1. Add your [API key](https://platform.openai.com/account/api-keys) and OpenAI API Endpoint(optional) to the newly created `.env` file; -3. Install dependencies and start the dev server; +1. Install dependencies and start the dev server; ```bash pnpm i && pnpm dev ``` +### Database Setup + +1. Install prisma + + ```bash + pnpm install prisma --save-dev + ``` + +1. Install prisma client + + ```bash + pnpm install @prisma/client + ``` + +1. Generate prisma client from the model + + ```bash + pnpm prisma generate + ``` + +1. Seed data + + ```bash + pnpm install typescript ts-node @types/node --save-dev + ``` + + ```bash + pnpm prisma db seed + ``` + ## Common Questions
How to self host SQL Chat? diff --git a/package.json b/package.json index 35e5b803..814fcd38 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.10.6", "@mui/material": "^5.11.14", "@mui/styled-engine-sc": "^5.11.11", + "@prisma/client": "4.13.0", "@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-popover": "^1.0.5", "@radix-ui/react-select": "^1.2.1", @@ -48,7 +49,7 @@ "@tailwindcss/typography": "^0.5.9", "@types/lodash-es": "^4.17.7", "@types/mssql": "^8.1.2", - "@types/node": "^18.11.18", + "@types/node": "^18.15.3", "@types/pg": "^8.6.6", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.11", @@ -61,8 +62,13 @@ "mysql2": "^3.2.0", "pg": "^8.10.0", "postcss": "^8.4.20", + "prisma": "^4.13.0", "react-syntax-highlighter": "^15.5.0", "tailwindcss": "^3.2.4", - "typescript": "^4.9.4" + "ts-node": "^10.9.1", + "typescript": "^4.9.5" + }, + "prisma": { + "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1db2101..989a7704 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ dependencies: '@mui/styled-engine-sc': specifier: ^5.11.11 version: 5.11.11(styled-components@5.3.9) + '@prisma/client': + specifier: 4.13.0 + version: 4.13.0(prisma@4.13.0) '@radix-ui/react-dropdown-menu': specifier: ^2.0.4 version: 2.0.4(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) @@ -115,7 +118,7 @@ devDependencies: specifier: ^8.1.2 version: 8.1.2 '@types/node': - specifier: ^18.11.18 + specifier: ^18.15.3 version: 18.15.3 '@types/pg': specifier: ^8.6.6 @@ -153,14 +156,20 @@ devDependencies: postcss: specifier: ^8.4.20 version: 8.4.21 + prisma: + specifier: ^4.13.0 + version: 4.13.0 react-syntax-highlighter: specifier: ^15.5.0 version: 15.5.0(react@18.2.0) tailwindcss: specifier: ^3.2.4 - version: 3.2.7(postcss@8.4.21) + version: 3.2.7(postcss@8.4.21)(ts-node@10.9.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@18.15.3)(typescript@4.9.5) typescript: - specifier: ^4.9.4 + specifier: ^4.9.5 version: 4.9.5 packages: @@ -458,6 +467,13 @@ packages: to-fast-properties: 2.0.0 dev: false + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + /@emotion/babel-plugin@11.10.6: resolution: {integrity: sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==} dependencies: @@ -650,7 +666,6 @@ packages: /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} - dev: false /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} @@ -659,7 +674,6 @@ packages: /@jridgewell/sourcemap-codec@1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: false /@jridgewell/trace-mapping@0.3.17: resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} @@ -668,6 +682,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: false + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + /@js-joda/core@5.5.3: resolution: {integrity: sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ==} dev: true @@ -1001,6 +1022,28 @@ packages: resolution: {integrity: sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==} dev: false + /@prisma/client@4.13.0(prisma@4.13.0): + resolution: {integrity: sha512-YaiiICcRB2hatxsbnfB66uWXjcRw3jsZdlAVxmx0cFcTc/Ad/sKdHCcWSnqyDX47vAewkjRFwiLwrOUjswVvmA==} + engines: {node: '>=14.17'} + requiresBuild: true + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + dependencies: + '@prisma/engines-version': 4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a + prisma: 4.13.0 + dev: false + + /@prisma/engines-version@4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a: + resolution: {integrity: sha512-fsQlbkhPJf08JOzKoyoD9atdUijuGBekwoOPZC3YOygXEml1MTtgXVpnUNchQlRSY82OQ6pSGQ9PxUe4arcSLQ==} + dev: false + + /@prisma/engines@4.13.0: + resolution: {integrity: sha512-HrniowHRZXHuGT9XRgoXEaP2gJLXM5RMoItaY2PkjvuZ+iHc0Zjbm/302MB8YsPdWozAPHHn+jpFEcEn71OgPw==} + requiresBuild: true + /@radix-ui/number@1.0.0: resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==} dependencies: @@ -1448,7 +1491,7 @@ packages: lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.2.7(postcss@8.4.21) + tailwindcss: 3.2.7(postcss@8.4.21)(ts-node@10.9.1) dev: true /@tediousjs/connection-string@0.4.2: @@ -1460,6 +1503,22 @@ packages: engines: {node: '>= 10'} dev: true + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.3: + resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + dev: true + /@types/debug@4.1.7: resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} dependencies: @@ -1668,6 +1727,11 @@ packages: engines: {node: '>=0.4.0'} dev: true + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} @@ -1725,6 +1789,10 @@ packages: picomatch: 2.3.1 dev: true + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} dev: true @@ -2089,6 +2157,10 @@ packages: yaml: 1.10.2 dev: false + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2251,6 +2323,11 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} engines: {node: '>=0.3.1'} @@ -3528,6 +3605,10 @@ packages: engines: {node: '>=12'} dev: true + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + /markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} dev: false @@ -4328,7 +4409,7 @@ packages: postcss: 8.4.21 dev: true - /postcss-load-config@3.1.4(postcss@8.4.21): + /postcss-load-config@3.1.4(postcss@8.4.21)(ts-node@10.9.1): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -4342,6 +4423,7 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.21 + ts-node: 10.9.1(@types/node@18.15.3)(typescript@4.9.5) yaml: 1.10.2 dev: true @@ -4419,6 +4501,14 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prisma@4.13.0: + resolution: {integrity: sha512-L9mqjnSmvWIRCYJ9mQkwCtj4+JDYYTdhoyo8hlsHNDXaZLh/b4hR0IoKIBbTKxZuyHQzLopb/+0Rvb69uGV7uA==} + engines: {node: '>=14.17'} + hasBin: true + requiresBuild: true + dependencies: + '@prisma/engines': 4.13.0 + /prismjs@1.27.0: resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} engines: {node: '>=6'} @@ -5137,7 +5227,7 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /tailwindcss@3.2.7(postcss@8.4.21): + /tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1): resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} engines: {node: '>=12.13.0'} hasBin: true @@ -5161,7 +5251,7 @@ packages: postcss: 8.4.21 postcss-import: 14.1.0(postcss@8.4.21) postcss-js: 4.0.1(postcss@8.4.21) - postcss-load-config: 3.1.4(postcss@8.4.21) + postcss-load-config: 3.1.4(postcss@8.4.21)(ts-node@10.9.1) postcss-nested: 6.0.0(postcss@8.4.21) postcss-selector-parser: 6.0.11 postcss-value-parser: 4.2.0 @@ -5233,6 +5323,37 @@ packages: resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} dev: false + /ts-node@10.9.1(@types/node@18.15.3)(typescript@4.9.5): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 18.15.3 + acorn: 8.8.2 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsconfig-paths@3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: @@ -5459,6 +5580,10 @@ packages: sade: 1.8.1 dev: false + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + /v8-compile-cache@2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: true @@ -5545,6 +5670,11 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + /zustand@4.3.6(react@18.2.0): resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==} engines: {node: '>=12.7.0'} diff --git a/prisma/migrations/20230423161119_init/migration.sql b/prisma/migrations/20230423161119_init/migration.sql new file mode 100644 index 00000000..13edbf6b --- /dev/null +++ b/prisma/migrations/20230423161119_init/migration.sql @@ -0,0 +1,33 @@ +-- CreateTable +CREATE TABLE "Chat" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ctx" JSONB NOT NULL, + + CONSTRAINT "Chat_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Prompt" ( + "id" TEXT NOT NULL, + "chatId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "q" TEXT NOT NULL, + "a" TEXT NOT NULL, + "upvote" BOOLEAN NOT NULL DEFAULT false, + "downvote" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "Prompt_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "Chat_createdAt_idx" ON "Chat"("createdAt"); + +-- CreateIndex +CREATE INDEX "Prompt_chatId_idx" ON "Prompt"("chatId"); + +-- CreateIndex +CREATE INDEX "Prompt_createdAt_idx" ON "Prompt"("createdAt"); + +-- AddForeignKey +ALTER TABLE "Prompt" ADD CONSTRAINT "Prompt_chatId_fkey" FOREIGN KEY ("chatId") REFERENCES "Chat"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 00000000..605dcf88 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,34 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("TELEMETRY_DATABASE_URL") +} + +model Chat { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + ctx Json + prompts Prompt[] + + @@index([createdAt]) +} + +model Prompt { + id String @id @default(uuid()) + chat Chat @relation(fields: [chatId], references: [id]) + chatId String + createdAt DateTime @default(now()) + q String + a String + upvote Boolean @default(false) + downvote Boolean @default(false) + + @@index([chatId]) + @@index([createdAt]) +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 00000000..b0741ac3 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,70 @@ +import { PrismaClient, Prisma } from '@prisma/client' +import { v4 as uuidv4 } from "uuid"; + +const prisma = new PrismaClient() + +const chatData: Prisma.ChatCreateInput[] = [ + { + id: uuidv4(), + createdAt: new Date(), + ctx: {}, + prompts: { + create: [ + { + id: uuidv4(), + createdAt: new Date(), + q: "Hello", + a: "What can I help you with today?", + upvote: true, + downvote: false, + }, + { + id: uuidv4(), + createdAt: new Date(), + q: "How are you?", + a: "Fine, thank you, and you?", + upvote: true, + downvote: false, + }, + ], + }, + }, + { + id: uuidv4(), + createdAt: new Date(), + ctx: {}, + prompts: { + create: [ + { + id: uuidv4(), + createdAt: new Date(), + q: "Tell me a joke", + a: "What do you call a fake noodle? An Impasta.", + upvote: true, + downvote: false, + }, + ], + }, + }, +] + +async function main() { + console.log(`Start seeding ...`) + for (const c of chatData) { + const chat = await prisma.chat.create({ + data: c, + }) + console.log(`Created chat with id: ${chat.id}`) + } + console.log(`Seeding finished.`) +} + +main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) From 5eb865daacd5db95bf7ec7fc72777fea6770d9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tianzhou=20=28=E5=A4=A9=E8=88=9F=29?= Date: Mon, 24 Apr 2023 01:37:58 +0800 Subject: [PATCH 009/237] chore: update readme (#61) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8d89d6bc..8a8a7d48 100644 --- a/README.md +++ b/README.md @@ -82,19 +82,19 @@ docker run --name sqlchat --platform linux/amd64 --env OPENAI_API_KEY=xxx --env pnpm install prisma --save-dev ``` -1. Install prisma client +2. Install prisma client ```bash pnpm install @prisma/client ``` -1. Generate prisma client from the model +3. Generate prisma client from the model ```bash pnpm prisma generate ``` -1. Seed data +4. Seed data ```bash pnpm install typescript ts-node @types/node --save-dev From 4a49b9d9c835f5b89ee587c8ea07e04a8f00d088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tianzhou=20=28=E5=A4=A9=E8=88=9F=29?= Date: Mon, 24 Apr 2023 01:40:20 +0800 Subject: [PATCH 010/237] chore: change to USAGE_DATABASE_URL (#62) * chore: update readme * chore: change to USAGE_DATABASE_URL --- .env.example | 2 +- prisma/schema.prisma | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 52d79336..91930f6c 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,4 @@ OPENAI_API_KEY=YOUR_API_KEY # Optional. # OPENAI_API_ENDPOINT=YOUR_API_ENDPOINT -# TELEMETRY_DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=sqlchat +# USAGE_DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=sqlchat_usage diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 605dcf88..c21929b3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,7 +7,7 @@ generator client { datasource db { provider = "postgresql" - url = env("TELEMETRY_DATABASE_URL") + url = env("USAGE_DATABASE_URL") } model Chat { From c74302a4a6ff3a7eafa115763d78593b08537fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tianzhou=20=28=E5=A4=A9=E8=88=9F=29?= Date: Mon, 24 Apr 2023 01:48:26 +0800 Subject: [PATCH 011/237] chore: add vercel-build script for CI/CD (#63) * chore: update readme * chore: change to USAGE_DATABASE_URL * chore: add vercel-build --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 814fcd38..446c36ba 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "build": "next build", "export": "next export", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "vercel-build": "prisma generate && prisma migrate deploy && next build" }, "dependencies": { "@emotion/react": "^11.10.6", From a82308bd8b8fd4efb77f19f167419d1bc635aa45 Mon Sep 17 00:00:00 2001 From: CorrectRoadH Date: Mon, 24 Apr 2023 14:50:54 +0800 Subject: [PATCH 012/237] feat: make select component scrollable (#58) * implmemnt scroll select #56 * limit the max height * mix two solutio to implment final effect --- package.json | 1 + pnpm-lock.yaml | 471 ++++++++++++++++++++++++---------- src/components/kit/Select.tsx | 39 +-- 3 files changed, 355 insertions(+), 156 deletions(-) diff --git a/package.json b/package.json index 446c36ba..66dd7386 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@prisma/client": "4.13.0", "@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-popover": "^1.0.5", + "@radix-ui/react-scroll-area": "^1.0.3", "@radix-ui/react-select": "^1.2.1", "@radix-ui/react-tooltip": "^1.0.5", "@vercel/analytics": "^0.1.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 989a7704..64902549 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ dependencies: '@radix-ui/react-popover': specifier: ^1.0.5 version: 1.0.5(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-scroll-area': + specifier: ^1.0.3 + version: registry.npmmirror.com/@radix-ui/react-scroll-area@1.0.3(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-select': specifier: ^1.2.1 version: 1.2.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) @@ -880,123 +883,6 @@ packages: glob: 7.1.7 dev: true - /@next/swc-android-arm-eabi@13.2.4: - resolution: {integrity: sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==} - engines: {node: '>= 10'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@next/swc-android-arm64@13.2.4: - resolution: {integrity: sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@next/swc-darwin-arm64@13.2.4: - resolution: {integrity: sha512-S6vBl+OrInP47TM3LlYx65betocKUUlTZDDKzTiRDbsRESeyIkBtZ6Qi5uT2zQs4imqllJznVjFd1bXLx3Aa6A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@next/swc-darwin-x64@13.2.4: - resolution: {integrity: sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@next/swc-freebsd-x64@13.2.4: - resolution: {integrity: sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm-gnueabihf@13.2.4: - resolution: {integrity: sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm64-gnu@13.2.4: - resolution: {integrity: sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm64-musl@13.2.4: - resolution: {integrity: sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-x64-gnu@13.2.4: - resolution: {integrity: sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-x64-musl@13.2.4: - resolution: {integrity: sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-arm64-msvc@13.2.4: - resolution: {integrity: sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-ia32-msvc@13.2.4: - resolution: {integrity: sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-x64-msvc@13.2.4: - resolution: {integrity: sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2080,7 +1966,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: registry.npmmirror.com/fsevents@2.3.2 dev: true /client-only@0.0.1: @@ -2907,14 +2793,6 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} @@ -4138,19 +4016,19 @@ packages: react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(react@18.2.0) optionalDependencies: - '@next/swc-android-arm-eabi': 13.2.4 - '@next/swc-android-arm64': 13.2.4 - '@next/swc-darwin-arm64': 13.2.4 - '@next/swc-darwin-x64': 13.2.4 - '@next/swc-freebsd-x64': 13.2.4 - '@next/swc-linux-arm-gnueabihf': 13.2.4 - '@next/swc-linux-arm64-gnu': 13.2.4 - '@next/swc-linux-arm64-musl': 13.2.4 - '@next/swc-linux-x64-gnu': 13.2.4 - '@next/swc-linux-x64-musl': 13.2.4 - '@next/swc-win32-arm64-msvc': 13.2.4 - '@next/swc-win32-ia32-msvc': 13.2.4 - '@next/swc-win32-x64-msvc': 13.2.4 + '@next/swc-android-arm-eabi': registry.npmmirror.com/@next/swc-android-arm-eabi@13.2.4 + '@next/swc-android-arm64': registry.npmmirror.com/@next/swc-android-arm64@13.2.4 + '@next/swc-darwin-arm64': registry.npmmirror.com/@next/swc-darwin-arm64@13.2.4 + '@next/swc-darwin-x64': registry.npmmirror.com/@next/swc-darwin-x64@13.2.4 + '@next/swc-freebsd-x64': registry.npmmirror.com/@next/swc-freebsd-x64@13.2.4 + '@next/swc-linux-arm-gnueabihf': registry.npmmirror.com/@next/swc-linux-arm-gnueabihf@13.2.4 + '@next/swc-linux-arm64-gnu': registry.npmmirror.com/@next/swc-linux-arm64-gnu@13.2.4 + '@next/swc-linux-arm64-musl': registry.npmmirror.com/@next/swc-linux-arm64-musl@13.2.4 + '@next/swc-linux-x64-gnu': registry.npmmirror.com/@next/swc-linux-x64-gnu@13.2.4 + '@next/swc-linux-x64-musl': registry.npmmirror.com/@next/swc-linux-x64-musl@13.2.4 + '@next/swc-win32-arm64-msvc': registry.npmmirror.com/@next/swc-win32-arm64-msvc@13.2.4 + '@next/swc-win32-ia32-msvc': registry.npmmirror.com/@next/swc-win32-ia32-msvc@13.2.4 + '@next/swc-win32-x64-msvc': registry.npmmirror.com/@next/swc-win32-x64-msvc@13.2.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -5694,3 +5572,318 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false + + registry.npmmirror.com/@babel/runtime@7.21.0: + resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz} + name: '@babel/runtime' + version: 7.21.0 + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: registry.npmmirror.com/regenerator-runtime@0.13.11 + dev: false + + registry.npmmirror.com/@next/swc-android-arm-eabi@13.2.4: + resolution: {integrity: sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz} + name: '@next/swc-android-arm-eabi' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-android-arm64@13.2.4: + resolution: {integrity: sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz} + name: '@next/swc-android-arm64' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-darwin-arm64@13.2.4: + resolution: {integrity: sha512-S6vBl+OrInP47TM3LlYx65betocKUUlTZDDKzTiRDbsRESeyIkBtZ6Qi5uT2zQs4imqllJznVjFd1bXLx3Aa6A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.4.tgz} + name: '@next/swc-darwin-arm64' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-darwin-x64@13.2.4: + resolution: {integrity: sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.4.tgz} + name: '@next/swc-darwin-x64' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-freebsd-x64@13.2.4: + resolution: {integrity: sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz} + name: '@next/swc-freebsd-x64' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-linux-arm-gnueabihf@13.2.4: + resolution: {integrity: sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz} + name: '@next/swc-linux-arm-gnueabihf' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-linux-arm64-gnu@13.2.4: + resolution: {integrity: sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.4.tgz} + name: '@next/swc-linux-arm64-gnu' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-linux-arm64-musl@13.2.4: + resolution: {integrity: sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.4.tgz} + name: '@next/swc-linux-arm64-musl' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-linux-x64-gnu@13.2.4: + resolution: {integrity: sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.4.tgz} + name: '@next/swc-linux-x64-gnu' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-linux-x64-musl@13.2.4: + resolution: {integrity: sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.4.tgz} + name: '@next/swc-linux-x64-musl' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-win32-arm64-msvc@13.2.4: + resolution: {integrity: sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.4.tgz} + name: '@next/swc-win32-arm64-msvc' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-win32-ia32-msvc@13.2.4: + resolution: {integrity: sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.4.tgz} + name: '@next/swc-win32-ia32-msvc' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@next/swc-win32-x64-msvc@13.2.4: + resolution: {integrity: sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.4.tgz} + name: '@next/swc-win32-x64-msvc' + version: 13.2.4 + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + registry.npmmirror.com/@radix-ui/number@1.0.0: + resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/number/-/number-1.0.0.tgz} + name: '@radix-ui/number' + version: 1.0.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + dev: false + + registry.npmmirror.com/@radix-ui/primitive@1.0.0: + resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.0.tgz} + name: '@radix-ui/primitive' + version: 1.0.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + dev: false + + registry.npmmirror.com/@radix-ui/react-compose-refs@1.0.0(react@18.2.0): + resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz} + id: registry.npmmirror.com/@radix-ui/react-compose-refs/1.0.0 + name: '@radix-ui/react-compose-refs' + version: 1.0.0 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + react: 18.2.0 + dev: false + + registry.npmmirror.com/@radix-ui/react-context@1.0.0(react@18.2.0): + resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.0.tgz} + id: registry.npmmirror.com/@radix-ui/react-context/1.0.0 + name: '@radix-ui/react-context' + version: 1.0.0 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + react: 18.2.0 + dev: false + + registry.npmmirror.com/@radix-ui/react-direction@1.0.0(react@18.2.0): + resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz} + id: registry.npmmirror.com/@radix-ui/react-direction/1.0.0 + name: '@radix-ui/react-direction' + version: 1.0.0 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + react: 18.2.0 + dev: false + + registry.npmmirror.com/@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz} + id: registry.npmmirror.com/@radix-ui/react-presence/1.0.0 + name: '@radix-ui/react-presence' + version: 1.0.0 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + '@radix-ui/react-compose-refs': registry.npmmirror.com/@radix-ui/react-compose-refs@1.0.0(react@18.2.0) + '@radix-ui/react-use-layout-effect': registry.npmmirror.com/@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + registry.npmmirror.com/@radix-ui/react-primitive@1.0.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz} + id: registry.npmmirror.com/@radix-ui/react-primitive/1.0.2 + name: '@radix-ui/react-primitive' + version: 1.0.2 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + '@radix-ui/react-slot': registry.npmmirror.com/@radix-ui/react-slot@1.0.1(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + registry.npmmirror.com/@radix-ui/react-scroll-area@1.0.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sBX9j8Q+0/jReNObEAveKIGXJtk3xUoSIx4cMKygGtO128QJyVDn01XNOFsyvihKDCTcu7SINzQ2jPAZEhIQtw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.3.tgz} + id: registry.npmmirror.com/@radix-ui/react-scroll-area/1.0.3 + name: '@radix-ui/react-scroll-area' + version: 1.0.3 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + '@radix-ui/number': registry.npmmirror.com/@radix-ui/number@1.0.0 + '@radix-ui/primitive': registry.npmmirror.com/@radix-ui/primitive@1.0.0 + '@radix-ui/react-compose-refs': registry.npmmirror.com/@radix-ui/react-compose-refs@1.0.0(react@18.2.0) + '@radix-ui/react-context': registry.npmmirror.com/@radix-ui/react-context@1.0.0(react@18.2.0) + '@radix-ui/react-direction': registry.npmmirror.com/@radix-ui/react-direction@1.0.0(react@18.2.0) + '@radix-ui/react-presence': registry.npmmirror.com/@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': registry.npmmirror.com/@radix-ui/react-primitive@1.0.2(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': registry.npmmirror.com/@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0) + '@radix-ui/react-use-layout-effect': registry.npmmirror.com/@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + registry.npmmirror.com/@radix-ui/react-slot@1.0.1(react@18.2.0): + resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.1.tgz} + id: registry.npmmirror.com/@radix-ui/react-slot/1.0.1 + name: '@radix-ui/react-slot' + version: 1.0.1 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + '@radix-ui/react-compose-refs': registry.npmmirror.com/@radix-ui/react-compose-refs@1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + + registry.npmmirror.com/@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0): + resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz} + id: registry.npmmirror.com/@radix-ui/react-use-callback-ref/1.0.0 + name: '@radix-ui/react-use-callback-ref' + version: 1.0.0 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + react: 18.2.0 + dev: false + + registry.npmmirror.com/@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0): + resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz} + id: registry.npmmirror.com/@radix-ui/react-use-layout-effect/1.0.0 + name: '@radix-ui/react-use-layout-effect' + version: 1.0.0 + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': registry.npmmirror.com/@babel/runtime@7.21.0 + react: 18.2.0 + dev: false + + registry.npmmirror.com/fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz} + name: fsevents + version: 2.3.2 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz} + name: regenerator-runtime + version: 0.13.11 + dev: false diff --git a/src/components/kit/Select.tsx b/src/components/kit/Select.tsx index 70f28bc7..b8f03d0c 100644 --- a/src/components/kit/Select.tsx +++ b/src/components/kit/Select.tsx @@ -1,4 +1,5 @@ import * as SelectUI from "@radix-ui/react-select"; +import * as ScrollArea from '@radix-ui/react-scroll-area'; import React from "react"; import Icon from "../Icon"; @@ -36,23 +37,27 @@ const Select = (props: Props) => { }} position="popper" > - - - {placeholder && {placeholder}} - {itemList.map((item) => ( - - {item.label} - - - - - ))} - - + 7 ? "h-80 overflow-hidden" : "max-h-80 overflow-auto"} border dark:border-zinc-800 rounded-lg drop-shadow-lg`} type="auto"> + + + + {placeholder && {placeholder}} + {itemList.map((item) => ( + + {item.label} + + + + + ))} + + + + From 00e3040670dfd127b44ef4dc0486fbfecadc7a5f Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 24 Apr 2023 18:07:13 +0800 Subject: [PATCH 013/237] chore: update usage database schema --- README.md | 88 ++++++++++--------- .../20230424100325_add_message/migration.sql | 33 +++++++ prisma/schema.prisma | 8 +- prisma/seed.ts | 47 ++++------ src/pages/api/chat.ts | 2 +- 5 files changed, 103 insertions(+), 75 deletions(-) create mode 100644 prisma/migrations/20230424100325_add_message/migration.sql diff --git a/README.md b/README.md index 8a8a7d48..ee8397dd 100644 --- a/README.md +++ b/README.md @@ -76,35 +76,49 @@ docker run --name sqlchat --platform linux/amd64 --env OPENAI_API_KEY=xxx --env ### Database Setup -1. Install prisma +1. Install dependencies - ```bash - pnpm install prisma --save-dev - ``` + ```bash + pnpm i + ``` -2. Install prisma client +1. Generate prisma client from the model - ```bash - pnpm install @prisma/client - ``` + ```bash + pnpm prisma generate + ``` -3. Generate prisma client from the model +1. Seed data - ```bash - pnpm prisma generate - ``` + ```bash + pnpm prisma db seed + ``` -4. Seed data +## Star History - ```bash - pnpm install typescript ts-node @types/node --save-dev - ``` +[![Star History Chart](https://api.star-history.com/svg?repos=sqlchat/sqlchat&type=Date)](https://star-history.com/#sqlchat/sqlchat&Date) - ```bash - pnpm prisma db seed - ``` +## Community -## Common Questions +[![Hang out on Discord](https://img.shields.io/badge/%20-Hang%20out%20on%20Discord-5865F2?style=for-the-badge&logo=discord&labelColor=EEEEEE)](https://discord.gg/z6kakemDjm) + +[![Follow us on Twitter](https://img.shields.io/badge/Follow%20us%20on%20Twitter-1DA1F2?style=for-the-badge&logo=twitter&labelColor=EEEEEE)](https://twitter.com/Bytebase) + +sqlchat + +## Sponsors + +

+ + + +

+ +## License + +This project is under the BSL License. See the [LICENSE](LICENSE) file for the full license text. + +## FAQ
How to self host SQL Chat?

@@ -122,34 +136,24 @@ docker run --name sqlchat --platform linux/amd64 --env OPENAI_API_KEY=xxx --env

-
It always says that I have a network connection issue? +
How to use my OpenAI API key?

-Please make sure you have a stable network connection which can access the OpenAI API endpoint. If you cannot access the OpenAI API endpoint, you can try to set the `OPENAI_API_ENDPOINT` in UI or environment variable. +- You can set the `OPENAI_API_KEY` in environment variable. -

-
- -## Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=sqlchat/sqlchat&type=Date)](https://star-history.com/#sqlchat/sqlchat&Date) - -## Community - -[![Hang out on Discord](https://img.shields.io/badge/%20-Hang%20out%20on%20Discord-5865F2?style=for-the-badge&logo=discord&labelColor=EEEEEE)](https://discord.gg/z6kakemDjm) - -[![Follow us on Twitter](https://img.shields.io/badge/Follow%20us%20on%20Twitter-1DA1F2?style=for-the-badge&logo=twitter&labelColor=EEEEEE)](https://twitter.com/Bytebase) + ```bash + docker run --name sqlchat --platform linux/amd64 --env OPENAI_API_KEY=xxx -p 3000:3000 sqlchat/sqlchat + ``` -sqlchat +- You can set the `OPENAI_API_KEY` in setting dialog. -## Sponsors +

+
+
It always says that I have a network connection issue?

- - - -

-## License +Please make sure you have a stable network connection which can access the OpenAI API endpoint. If you cannot access the OpenAI API endpoint, you can try to set the `OPENAI_API_ENDPOINT` in UI or environment variable. -This project is under the BSL License. See the [LICENSE](LICENSE) file for the full license text. +

+
diff --git a/prisma/migrations/20230424100325_add_message/migration.sql b/prisma/migrations/20230424100325_add_message/migration.sql new file mode 100644 index 00000000..680c11be --- /dev/null +++ b/prisma/migrations/20230424100325_add_message/migration.sql @@ -0,0 +1,33 @@ +/* + Warnings: + + - You are about to drop the `Prompt` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Prompt" DROP CONSTRAINT "Prompt_chatId_fkey"; + +-- DropTable +DROP TABLE "Prompt"; + +-- CreateTable +CREATE TABLE "Message" ( + "id" TEXT NOT NULL, + "chatId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "role" TEXT NOT NULL, + "content" TEXT NOT NULL, + "upvote" BOOLEAN NOT NULL DEFAULT false, + "downvote" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "Message_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "Message_chatId_idx" ON "Message"("chatId"); + +-- CreateIndex +CREATE INDEX "Message_createdAt_idx" ON "Message"("createdAt"); + +-- AddForeignKey +ALTER TABLE "Message" ADD CONSTRAINT "Message_chatId_fkey" FOREIGN KEY ("chatId") REFERENCES "Chat"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c21929b3..241fdec5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,18 +14,18 @@ model Chat { id String @id @default(uuid()) createdAt DateTime @default(now()) ctx Json - prompts Prompt[] + messages Message[] @@index([createdAt]) } -model Prompt { +model Message { id String @id @default(uuid()) chat Chat @relation(fields: [chatId], references: [id]) chatId String createdAt DateTime @default(now()) - q String - a String + role String + content String upvote Boolean @default(false) downvote Boolean @default(false) diff --git a/prisma/seed.ts b/prisma/seed.ts index b0741ac3..e01d77bd 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,70 +1,61 @@ -import { PrismaClient, Prisma } from '@prisma/client' +import { PrismaClient, Prisma } from "@prisma/client"; import { v4 as uuidv4 } from "uuid"; -const prisma = new PrismaClient() +const prisma = new PrismaClient(); const chatData: Prisma.ChatCreateInput[] = [ { id: uuidv4(), createdAt: new Date(), ctx: {}, - prompts: { + messages: { create: [ { id: uuidv4(), createdAt: new Date(), - q: "Hello", - a: "What can I help you with today?", + role: "system", + content: "You are a bot", upvote: true, downvote: false, }, { id: uuidv4(), createdAt: new Date(), - q: "How are you?", - a: "Fine, thank you, and you?", + role: "user", + content: "What can I help you with today?", upvote: true, downvote: false, }, - ], - }, - }, - { - id: uuidv4(), - createdAt: new Date(), - ctx: {}, - prompts: { - create: [ { id: uuidv4(), createdAt: new Date(), - q: "Tell me a joke", - a: "What do you call a fake noodle? An Impasta.", + role: "assistant", + content: "Hello", upvote: true, downvote: false, }, ], }, }, -] +]; async function main() { - console.log(`Start seeding ...`) + console.log(`Start seeding ...`); for (const c of chatData) { const chat = await prisma.chat.create({ data: c, - }) - console.log(`Created chat with id: ${chat.id}`) + }); + console.log(`Created chat with id: ${chat.id}`); } - console.log(`Seeding finished.`) + console.log(`Seeding finished.`); } main() .then(async () => { - await prisma.$disconnect() + await prisma.$disconnect(); }) .catch(async (e) => { - console.error(e) - await prisma.$disconnect() - process.exit(1) - }) + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/src/pages/api/chat.ts b/src/pages/api/chat.ts index 106ed980..e7a0ac24 100644 --- a/src/pages/api/chat.ts +++ b/src/pages/api/chat.ts @@ -8,7 +8,7 @@ export const config = { const getApiEndpoint = (apiEndpoint: string) => { const url = new URL(apiEndpoint); - url.pathname = '/v1/chat/completions'; + url.pathname = "/v1/chat/completions"; return url; }; From e1847be56d90529a041a873a359f0a1b4abcd6ce Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 24 Apr 2023 18:24:55 +0800 Subject: [PATCH 014/237] feat: add usage api call --- src/components/ConversationView/index.tsx | 20 ++++++++++++ src/pages/api/usage.ts | 37 +++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/pages/api/usage.ts diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 4f79dda8..7d337efe 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -1,3 +1,4 @@ +import axios from "axios"; import { head, last } from "lodash-es"; import { useEffect, useRef, useState } from "react"; import { toast } from "react-hot-toast"; @@ -145,17 +146,26 @@ const ConversationView = () => { } prompt = promptGenerator(schema); } + + let usageMessageList: Message[] = []; let formatedMessageList = []; for (let i = messageList.length - 1; i >= 0; i--) { const message = messageList[i]; if (tokens < MAX_TOKENS) { tokens += countTextTokens(message.content); + usageMessageList.unshift(message); formatedMessageList.unshift({ role: message.creatorRole, content: message.content, }); } } + usageMessageList.unshift({ + id: generateUUID(), + createdAt: Date.now(), + creatorRole: CreatorRole.System, + content: prompt, + } as Message); formatedMessageList.unshift({ role: CreatorRole.System, content: prompt, @@ -169,6 +179,16 @@ const ConversationView = () => { }), }); + // Collect usage. + axios + .post("/api/usage", { + conversation: currentConversation, + messages: usageMessageList, + }) + .catch(() => { + // do nth + }); + if (!rawRes.ok) { console.error(rawRes); let errorMessage = "Failed to request message, please check your network."; diff --git a/src/pages/api/usage.ts b/src/pages/api/usage.ts new file mode 100644 index 00000000..3e2dfc52 --- /dev/null +++ b/src/pages/api/usage.ts @@ -0,0 +1,37 @@ +import { PrismaClient } from "@prisma/client"; +import { NextApiRequest, NextApiResponse } from "next"; +import { Conversation, Message } from "@/types"; + +const prisma = new PrismaClient(); + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== "POST") { + return res.status(405).json([]); + } + + const conversation = req.body.conversation as Conversation; + const messages = req.body.messages as Message[]; + try { + await prisma.chat.create({ + data: { + id: conversation.id, + createdAt: new Date(conversation.createdAt), + ctx: {}, + messages: { + create: messages.map((message) => ({ + id: message.id, + createdAt: new Date(message.createdAt), + role: message.creatorRole, + content: message.content, + upvote: true, + downvote: false, + })), + }, + }, + }); + } catch (err) { + console.error(err); + } + + res.status(200).json(true); +} From 75b000055695b245b915a63c117106ea378bbf49 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 24 Apr 2023 18:29:30 +0800 Subject: [PATCH 015/237] chore: update usage message --- src/components/ConversationView/index.tsx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 7d337efe..9e9d26ef 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -179,16 +179,6 @@ const ConversationView = () => { }), }); - // Collect usage. - axios - .post("/api/usage", { - conversation: currentConversation, - messages: usageMessageList, - }) - .catch(() => { - // do nth - }); - if (!rawRes.ok) { console.error(rawRes); let errorMessage = "Failed to request message, please check your network."; @@ -230,6 +220,17 @@ const ConversationView = () => { messageStore.updateMessage(message.id, { status: "DONE", }); + + usageMessageList.push(message); + // Collect usage. + axios + .post("/api/usage", { + conversation: currentConversation, + messages: usageMessageList, + }) + .catch(() => { + // do nth + }); }; return ( From f0965e6b736930087be86606e81a36e9b93c035b Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 24 Apr 2023 18:57:50 +0800 Subject: [PATCH 016/237] chore: update create usage data --- src/components/ConversationView/index.tsx | 4 +- src/pages/api/usage.ts | 45 ++++++++++++++++------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 9e9d26ef..b131c6ec 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -1,5 +1,5 @@ import axios from "axios"; -import { head, last } from "lodash-es"; +import { first, head, last } from "lodash-es"; import { useEffect, useRef, useState } from "react"; import { toast } from "react-hot-toast"; import { @@ -162,7 +162,7 @@ const ConversationView = () => { } usageMessageList.unshift({ id: generateUUID(), - createdAt: Date.now(), + createdAt: first(usageMessageList)?.createdAt || Date.now(), creatorRole: CreatorRole.System, content: prompt, } as Message); diff --git a/src/pages/api/usage.ts b/src/pages/api/usage.ts index 3e2dfc52..7f8cdff5 100644 --- a/src/pages/api/usage.ts +++ b/src/pages/api/usage.ts @@ -12,23 +12,40 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const conversation = req.body.conversation as Conversation; const messages = req.body.messages as Message[]; try { - await prisma.chat.create({ - data: { + const chat = await prisma.chat.findUnique({ + where: { id: conversation.id, - createdAt: new Date(conversation.createdAt), - ctx: {}, - messages: { - create: messages.map((message) => ({ - id: message.id, - createdAt: new Date(message.createdAt), - role: message.creatorRole, - content: message.content, - upvote: true, - downvote: false, - })), - }, }, }); + if (chat) { + await prisma.message.createMany({ + data: messages.map((message) => ({ + chatId: chat.id, + createdAt: new Date(message.createdAt), + role: message.creatorRole, + content: message.content, + upvote: true, + downvote: false, + })), + }); + } else { + await prisma.chat.create({ + data: { + id: conversation.id, + createdAt: new Date(conversation.createdAt), + ctx: {}, + messages: { + create: messages.map((message) => ({ + createdAt: new Date(message.createdAt), + role: message.creatorRole, + content: message.content, + upvote: true, + downvote: false, + })), + }, + }, + }); + } } catch (err) { console.error(err); } From f959cffbbd365c8b0a142f41f947c705b552bf42 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Mon, 24 Apr 2023 19:30:24 +0800 Subject: [PATCH 017/237] chore: hide quota overflow banner --- src/components/ConversationView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index b131c6ec..cb3ac303 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -242,7 +242,7 @@ const ConversationView = () => { >
- + {/* */}
From 455813525ad06aebdc6030549bc8487ec53de416 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 24 Apr 2023 20:05:41 +0800 Subject: [PATCH 018/237] chore: remove Quota banner --- src/components/ConversationView/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index cb3ac303..717f1ea0 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -19,7 +19,6 @@ import MessageView from "./MessageView"; import MessageTextarea from "./MessageTextarea"; import DataStorageBanner from "../DataStorageBanner"; import ProductHuntBanner from "../ProductHuntBanner"; -import QuotaOverflowBanner from "../QuotaOverflowBanner"; // The maximum number of tokens that can be sent to the OpenAI API. // reference: https://platform.openai.com/docs/api-reference/completions/create#completions/create-max_tokens @@ -242,7 +241,6 @@ const ConversationView = () => { >
- {/* */}
From 3264797661102a6ab18e444dd29241e582b529ed Mon Sep 17 00:00:00 2001 From: Tianzhou Chen Date: Mon, 24 Apr 2023 22:21:29 +0800 Subject: [PATCH 019/237] chore: add quota banner back, limiting again --- src/components/ConversationView/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 717f1ea0..b131c6ec 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -19,6 +19,7 @@ import MessageView from "./MessageView"; import MessageTextarea from "./MessageTextarea"; import DataStorageBanner from "../DataStorageBanner"; import ProductHuntBanner from "../ProductHuntBanner"; +import QuotaOverflowBanner from "../QuotaOverflowBanner"; // The maximum number of tokens that can be sent to the OpenAI API. // reference: https://platform.openai.com/docs/api-reference/completions/create#completions/create-max_tokens @@ -241,6 +242,7 @@ const ConversationView = () => { >
+
From 0471e40684de794b8c6193bcf954fb15d3ec6a16 Mon Sep 17 00:00:00 2001 From: CorrectRoadH Date: Mon, 24 Apr 2023 23:11:32 +0800 Subject: [PATCH 020/237] feat: optimize fetch database schema (#65) --- src/lib/connectors/index.ts | 2 +- src/lib/connectors/mssql/index.ts | 9 ++++----- src/lib/connectors/mysql/index.ts | 6 +++--- src/lib/connectors/postgres/index.ts | 8 ++++---- src/pages/api/connection/db_schema.ts | 11 +++++++---- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/lib/connectors/index.ts b/src/lib/connectors/index.ts index 24b6cd92..d84013ae 100644 --- a/src/lib/connectors/index.ts +++ b/src/lib/connectors/index.ts @@ -8,7 +8,7 @@ export interface Connector { execute: (databaseName: string, statement: string) => Promise; getDatabases: () => Promise; getTables: (databaseName: string) => Promise; - getTableStructure: (databaseName: string, tableName: string) => Promise; + getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => Promise; } export const newConnector = (connection: Connection): Connector => { diff --git a/src/lib/connectors/mssql/index.ts b/src/lib/connectors/mssql/index.ts index 58655470..4adb642e 100644 --- a/src/lib/connectors/mssql/index.ts +++ b/src/lib/connectors/mssql/index.ts @@ -75,7 +75,7 @@ const getTables = async (connection: Connection, databaseName: string): Promise< return tableList; }; -const getTableStructure = async (connection: Connection, databaseName: string, tableName: string): Promise => { +const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise => { const pool = await getMSSQLConnection(connection); const request = pool.request(); const { recordset } = await request.query( @@ -89,10 +89,9 @@ const getTableStructure = async (connection: Connection, databaseName: string, t `${row["COLUMN_NAME"]} ${row["DATA_TYPE"].toUpperCase()} ${String(row["IS_NULLABLE"]).toUpperCase() === "NO" ? "NOT NULL" : ""}` ); } - - return `CREATE TABLE [${tableName}] ( + structureFetched(tableName, `CREATE TABLE [${tableName}] ( ${columnList.join(",\n")} - );`; + );`); }; const newConnector = (connection: Connection): Connector => { @@ -101,7 +100,7 @@ const newConnector = (connection: Connection): Connector => { execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement), getDatabases: () => getDatabases(connection), getTables: (databaseName: string) => getTables(connection, databaseName), - getTableStructure: (databaseName: string, tableName: string) => getTableStructure(connection, databaseName, tableName), + getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched), }; }; diff --git a/src/lib/connectors/mysql/index.ts b/src/lib/connectors/mysql/index.ts index 0ffdc1bb..788799ce 100644 --- a/src/lib/connectors/mysql/index.ts +++ b/src/lib/connectors/mysql/index.ts @@ -80,14 +80,14 @@ const getTables = async (connection: Connection, databaseName: string): Promise< return tableList; }; -const getTableStructure = async (connection: Connection, databaseName: string, tableName: string): Promise => { +const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise => { const conn = await getMySQLConnection(connection); const [rows] = await conn.query(`SHOW CREATE TABLE \`${databaseName}\`.\`${tableName}\`;`); conn.destroy(); if (rows.length !== 1) { throw new Error("Unexpected number of rows."); } - return rows[0]["Create Table"] || ""; + structureFetched(tableName, rows[0]["Create Table"] || ""); }; const newConnector = (connection: Connection): Connector => { @@ -96,7 +96,7 @@ const newConnector = (connection: Connection): Connector => { execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement), getDatabases: () => getDatabases(connection), getTables: (databaseName: string) => getTables(connection, databaseName), - getTableStructure: (databaseName: string, tableName: string) => getTableStructure(connection, databaseName, tableName), + getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched), }; }; diff --git a/src/lib/connectors/postgres/index.ts b/src/lib/connectors/postgres/index.ts index 4612830a..fa4fd96a 100644 --- a/src/lib/connectors/postgres/index.ts +++ b/src/lib/connectors/postgres/index.ts @@ -83,7 +83,7 @@ const getTables = async (connection: Connection, databaseName: string): Promise< return tableList; }; -const getTableStructure = async (connection: Connection, databaseName: string, tableName: string): Promise => { +const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise => { connection.database = databaseName; const client = newPostgresClient(connection); await client.connect(); @@ -99,9 +99,9 @@ const getTableStructure = async (connection: Connection, databaseName: string, t `${row["column_name"]} ${row["data_type"].toUpperCase()} ${String(row["is_nullable"]).toUpperCase() === "NO" ? "NOT NULL" : ""}` ); } - return `CREATE TABLE \`${tableName}\` ( + structureFetched(tableName, `CREATE TABLE \`${tableName}\` ( ${columnList.join(",\n")} - );`; + );`); }; const newConnector = (connection: Connection): Connector => { @@ -110,7 +110,7 @@ const newConnector = (connection: Connection): Connector => { execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement), getDatabases: () => getDatabases(connection), getTables: (databaseName: string) => getTables(connection, databaseName), - getTableStructure: (databaseName: string, tableName: string) => getTableStructure(connection, databaseName, tableName), + getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched), }; }; diff --git a/src/pages/api/connection/db_schema.ts b/src/pages/api/connection/db_schema.ts index a801834a..b50c79ca 100644 --- a/src/pages/api/connection/db_schema.ts +++ b/src/pages/api/connection/db_schema.ts @@ -16,15 +16,18 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { const connector = newConnector(connection); const tableStructures: Table[] = []; const rawTableNameList = await connector.getTables(db); - for (const tableName of rawTableNameList) { - const structure = await connector.getTableStructure(db, tableName); + const structureFetched = (tableName:string, structure:string) => { tableStructures.push({ name: tableName, structure, }); } - res.status(200).json({ - data: tableStructures, + Promise.all(rawTableNameList.map(async (tableName) => + connector.getTableStructure(db, tableName, structureFetched) + )).then(() => { + res.status(200).json({ + data: tableStructures, + }); }); } catch (error: any) { res.status(400).json({ From daac415d35880caba6c034e02e64d244ee995439 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 24 Apr 2023 23:57:15 +0800 Subject: [PATCH 021/237] chore: update dockerfile --- Dockerfile | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 957ac04e..4f313ce6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -RUN yarn build +RUN yarn run docker-build # Production image, copy all the files and run next FROM base AS runner diff --git a/package.json b/package.json index 66dd7386..6d33f6ee 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "export": "next export", "start": "next start", "lint": "next lint", - "vercel-build": "prisma generate && prisma migrate deploy && next build" + "vercel-build": "prisma generate && prisma migrate deploy && next build", + "docker-build": "prisma generate && prisma migrate deploy && next build" }, "dependencies": { "@emotion/react": "^11.10.6", From 7233a50dff136fb62e9c2389e74bef8dc5db2cbc Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 25 Apr 2023 11:26:08 +0800 Subject: [PATCH 022/237] feat: add auth api key header --- .env.example | 5 +++-- src/components/ConversationView/index.tsx | 6 ++++++ src/env.ts | 2 ++ src/pages/api/chat.ts | 10 ++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/env.ts diff --git a/.env.example b/.env.example index 91930f6c..bd196ebe 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ # Do not share your OpenAI API key with anyone! It should remain a secret. -OPENAI_API_KEY=YOUR_API_KEY +OPENAI_API_KEY=YOUR_OPENAI_API_KEY # Optional. -# OPENAI_API_ENDPOINT=YOUR_API_ENDPOINT +# OPENAI_API_ENDPOINT=YOUR_OPENAI_API_ENDPOINT # USAGE_DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=sqlchat_usage +# API_KEY=YOUR_API_KEY diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index b131c6ec..d7ba409c 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -2,6 +2,7 @@ import axios from "axios"; import { first, head, last } from "lodash-es"; import { useEffect, useRef, useState } from "react"; import { toast } from "react-hot-toast"; +import { API_KEY } from "@/env"; import { getAssistantById, getPromptGeneratorOfAssistant, @@ -171,12 +172,17 @@ const ConversationView = () => { content: prompt, }); + const requestHeaders: any = {}; + if (API_KEY) { + requestHeaders["Authorization"] = `Bearer ${API_KEY}`; + } const rawRes = await fetch("/api/chat", { method: "POST", body: JSON.stringify({ messages: formatedMessageList, openAIApiConfig: settingStore.setting.openAIApiConfig, }), + headers: requestHeaders, }); if (!rawRes.ok) { diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 00000000..db7ba85c --- /dev/null +++ b/src/env.ts @@ -0,0 +1,2 @@ +// API_KEY is using to limit those authorized to use the API and protect the API endpoint. +export const API_KEY = process.env.API_KEY || ""; diff --git a/src/pages/api/chat.ts b/src/pages/api/chat.ts index e7a0ac24..a57ecc20 100644 --- a/src/pages/api/chat.ts +++ b/src/pages/api/chat.ts @@ -1,5 +1,6 @@ import { createParser, ParsedEvent, ReconnectInterval } from "eventsource-parser"; import { NextRequest } from "next/server"; +import { API_KEY } from "@/env"; import { openAIApiEndpoint, openAIApiKey } from "@/utils"; export const config = { @@ -13,6 +14,15 @@ const getApiEndpoint = (apiEndpoint: string) => { }; const handler = async (req: NextRequest) => { + if (API_KEY) { + const auth = req.headers.get("Authorization"); + if (!auth || auth !== `Bearer ${API_KEY}`) { + return new Response("Unauthorized", { + status: 401, + }); + } + } + const reqBody = await req.json(); const openAIApiConfig = reqBody.openAIApiConfig; const apiKey = openAIApiConfig?.key || openAIApiKey; From 1562a61e5e13d8ee667a574431a1437c62b04ca2 Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 25 Apr 2023 11:43:32 +0800 Subject: [PATCH 023/237] chore: update api key name --- .env.example | 2 +- src/env.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index bd196ebe..fa1a4696 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,4 @@ OPENAI_API_KEY=YOUR_OPENAI_API_KEY # Optional. # OPENAI_API_ENDPOINT=YOUR_OPENAI_API_ENDPOINT # USAGE_DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=sqlchat_usage -# API_KEY=YOUR_API_KEY +# NEXT_PUBLIC_API_KEY=YOUR_API_KEY diff --git a/src/env.ts b/src/env.ts index db7ba85c..991dc291 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,2 +1,2 @@ // API_KEY is using to limit those authorized to use the API and protect the API endpoint. -export const API_KEY = process.env.API_KEY || ""; +export const API_KEY = process.env.NEXT_PUBLIC_API_KEY || ""; From 49b15d350969808dcd362c2786d3683297b7bd32 Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 25 Apr 2023 15:12:36 +0800 Subject: [PATCH 024/237] feat: add clear conversation buttion --- src/components/ClearConversationButton.tsx | 27 +++++++++++++ .../ClearConversationConfirmModal.tsx | 39 +++++++++++++++++++ .../ConversationView/MessageTextarea.tsx | 2 +- src/components/ConversationView/index.tsx | 4 +- src/components/Icon.tsx | 2 + 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/components/ClearConversationButton.tsx create mode 100644 src/components/ClearConversationConfirmModal.tsx diff --git a/src/components/ClearConversationButton.tsx b/src/components/ClearConversationButton.tsx new file mode 100644 index 00000000..8f941e01 --- /dev/null +++ b/src/components/ClearConversationButton.tsx @@ -0,0 +1,27 @@ +import { useState } from "react"; +import { useConversationStore, useMessageStore } from "@/store"; +import Icon from "./Icon"; +import ClearConversationConfirmModal from "./ClearConversationConfirmModal"; + +const ClearConversationButton = () => { + const conversationStore = useConversationStore(); + const messageStore = useMessageStore(); + const [showConfirmModal, setShowConfirmModal] = useState(false); + const messageList = messageStore.messageList.filter((message) => message.conversationId === conversationStore.currentConversationId); + + return ( + <> + + + {showConfirmModal && setShowConfirmModal(false)} />} + + ); +}; + +export default ClearConversationButton; diff --git a/src/components/ClearConversationConfirmModal.tsx b/src/components/ClearConversationConfirmModal.tsx new file mode 100644 index 00000000..d7be4836 --- /dev/null +++ b/src/components/ClearConversationConfirmModal.tsx @@ -0,0 +1,39 @@ +import { useTranslation } from "react-i18next"; +import { useConversationStore, useMessageStore } from "@/store"; +import Modal from "./kit/Modal"; + +interface Props { + close: () => void; +} + +const ClearConversationConfirmModal = (props: Props) => { + const { close } = props; + const { t } = useTranslation(); + const conversationStore = useConversationStore(); + const messageStore = useMessageStore(); + + const handleClearMessages = () => { + messageStore.clearMessage((item) => item.conversationId !== conversationStore.currentConversationId); + close(); + }; + + return ( + +
+
+

Are you sure to clear the messages in current conversation?

+
+
+ + +
+
+
+ ); +}; + +export default ClearConversationConfirmModal; diff --git a/src/components/ConversationView/MessageTextarea.tsx b/src/components/ConversationView/MessageTextarea.tsx index 51571d87..faa6340a 100644 --- a/src/components/ConversationView/MessageTextarea.tsx +++ b/src/components/ConversationView/MessageTextarea.tsx @@ -73,7 +73,7 @@ const MessageTextarea = (props: Props) => { }; return ( -
+
{ messageList.map((message) => ) )}
-
+
+
diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index f5ddd801..5b2bb892 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -3,6 +3,7 @@ import * as Bi from "react-icons/bi"; import * as Bs from "react-icons/bs"; import * as Di from "react-icons/di"; import * as Fi from "react-icons/fi"; +import * as Gi from "react-icons/gi"; import * as Io from "react-icons/io"; import * as Io5 from "react-icons/io5"; @@ -12,6 +13,7 @@ const Icon = { ...Bs, ...Di, ...Fi, + ...Gi, ...Io, ...Io5, }; From a0972981e242dc05121ef8f3d129e2f072fe6dd9 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Tue, 25 Apr 2023 17:35:45 +0800 Subject: [PATCH 025/237] chore: remove quota overflow banner --- src/components/ConversationView/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 62c6f922..f739c9d0 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -21,7 +21,6 @@ import ClearConversationButton from "../ClearConversationButton"; import MessageTextarea from "./MessageTextarea"; import DataStorageBanner from "../DataStorageBanner"; import ProductHuntBanner from "../ProductHuntBanner"; -import QuotaOverflowBanner from "../QuotaOverflowBanner"; // The maximum number of tokens that can be sent to the OpenAI API. // reference: https://platform.openai.com/docs/api-reference/completions/create#completions/create-max_tokens @@ -249,7 +248,6 @@ const ConversationView = () => { >
-
From 4fb4786eceebd42442ca9ae8b1fbf351540565e3 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Tue, 25 Apr 2023 17:36:49 +0800 Subject: [PATCH 026/237] chore: remove producthunt banner --- src/components/ConversationView/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index f739c9d0..21f12b00 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -20,7 +20,6 @@ import MessageView from "./MessageView"; import ClearConversationButton from "../ClearConversationButton"; import MessageTextarea from "./MessageTextarea"; import DataStorageBanner from "../DataStorageBanner"; -import ProductHuntBanner from "../ProductHuntBanner"; // The maximum number of tokens that can be sent to the OpenAI API. // reference: https://platform.openai.com/docs/api-reference/completions/create#completions/create-max_tokens @@ -247,7 +246,6 @@ const ConversationView = () => { } relative w-full h-full max-h-full flex flex-col justify-start items-start overflow-y-auto bg-white dark:bg-zinc-800`} >
-
From 1e9b6d6ebcb9cd34bbbaf8a9cec13a3bd0da8534 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Tue, 25 Apr 2023 17:49:10 +0800 Subject: [PATCH 027/237] chore: swap discord and product hunt banner --- src/components/ConnectionSidebar.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/ConnectionSidebar.tsx b/src/components/ConnectionSidebar.tsx index 4c8eb0d5..0ffd70e9 100644 --- a/src/components/ConnectionSidebar.tsx +++ b/src/components/ConnectionSidebar.tsx @@ -288,7 +288,15 @@ const ConnectionSidebar = () => { {t("conversation.new-chat")}
- From 5427a7f4d120a1c170587011fbd44d86355db018 Mon Sep 17 00:00:00 2001 From: Tianzhou Chen Date: Tue, 25 Apr 2023 21:13:16 +0800 Subject: [PATCH 028/237] chore: update favicon with the right scale --- public/favicon.ico | Bin 0 -> 15406 bytes src/pages/index.tsx | 1 - 2 files changed, 1 deletion(-) create mode 100644 public/favicon.ico diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b58767bc6ae9039b92bbb916ca2dcf3ba3cf6cf1 GIT binary patch literal 15406 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 Ke_NnE7WjYLLE_&4 literal 0 HcmV?d00001 diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 5718c1fd..9b5f777d 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -21,7 +21,6 @@ const IndexPage: NextPage = () => {
SQL Chat - Chat-based SQL Client and Editor for the next decade - From 4f02a6f9add510caa6dfc6cbef0dbc5eb3ee9696 Mon Sep 17 00:00:00 2001 From: Tianzhou Chen Date: Tue, 25 Apr 2023 22:15:02 +0800 Subject: [PATCH 029/237] chore: use explicit prisma-build This is only used for building on vercel. Since there is no prerun hook to execute migration so we have to do the migration on build time. --- Dockerfile | 2 +- package.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4f313ce6..c1627366 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -RUN yarn run docker-build +RUN yarn run build # Production image, copy all the files and run next FROM base AS runner diff --git a/package.json b/package.json index 6d33f6ee..92727f6a 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "export": "next export", "start": "next start", "lint": "next lint", - "vercel-build": "prisma generate && prisma migrate deploy && next build", - "docker-build": "prisma generate && prisma migrate deploy && next build" + "prisma-build": "prisma generate && prisma migrate deploy && next build" }, "dependencies": { "@emotion/react": "^11.10.6", From 96724709bd724ca6f5e9bb4b54eefb5a709e2db1 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 26 Apr 2023 23:30:51 +0800 Subject: [PATCH 030/237] chore: prevent typescript errors when building docker file --- next.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/next.config.js b/next.config.js index b9f28963..a88c8c07 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ module.exports = { output: "standalone", + // Prevent TypeScript errors. + typescript: { + ignoreBuildErrors: true, + }, }; From 435e0bffeb842a78255cc51dbececefea6c11133 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Thu, 27 Apr 2023 16:41:47 +0800 Subject: [PATCH 031/237] chore: add quota banner and wording update --- src/components/ConversationView/index.tsx | 2 ++ src/locales/en.json | 4 ++-- src/locales/zh-Hant.json | 4 +++- src/locales/zh.json | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 21f12b00..7c4014a4 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -20,6 +20,7 @@ import MessageView from "./MessageView"; import ClearConversationButton from "../ClearConversationButton"; import MessageTextarea from "./MessageTextarea"; import DataStorageBanner from "../DataStorageBanner"; +import QuotaOverflowBanner from "../QuotaOverflowBanner"; // The maximum number of tokens that can be sent to the OpenAI API. // reference: https://platform.openai.com/docs/api-reference/completions/create#completions/create-max_tokens @@ -246,6 +247,7 @@ const ConversationView = () => { } relative w-full h-full max-h-full flex flex-col justify-start items-start overflow-y-auto bg-white dark:bg-zinc-800`} >
+
diff --git a/src/locales/en.json b/src/locales/en.json index 2a6ceb67..2375c9f8 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -73,7 +73,7 @@ "data-storage": "Connection settings are stored in your local browser", "non-select-sql-warning": "The current statement may be non-SELECT SQL, which will result in a database schema or data change. Make sure you know what you are doing.", "product-hunt": "🚀🚀🚀 We just launched on Product Hunt, please give us a vote! 🚀🚀🚀", - "quota-overflow": "Our quota is full and we are applying for additional credits. Please wait a moment.", - "use-my-key": "Use my key" + "quota-overflow": "Out of quota. Please provide your own OpenAI API key.", + "use-my-key": "Use my own key" } } diff --git a/src/locales/zh-Hant.json b/src/locales/zh-Hant.json index 61582b3f..0057a290 100644 --- a/src/locales/zh-Hant.json +++ b/src/locales/zh-Hant.json @@ -72,6 +72,8 @@ "banner": { "data-storage": "連線設定儲存在您的本地瀏覽器中", "non-select-sql-warning": "當前語句可能是非 SELECT SQL 語句,這將導致資料庫結構或資料的更改。請確保您知道自己在做什麼。", - "product-hunt": "🚀🚀🚀 我們剛在 Product Hunt 上推出,請給我們一個投票!🚀🚀🚀" + "product-hunt": "🚀🚀🚀 我們剛在 Product Hunt 上推出,請給我們一個投票!🚀🚀🚀", + "quota-overflow": "我們的 OpenAI 額度已用完,請提供您自己的 OpenAI API key。", + "use-my-key": "提供我自己的 key" } } diff --git a/src/locales/zh.json b/src/locales/zh.json index f770b573..83bc8db5 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -73,7 +73,7 @@ "data-storage": "连接设置存储在您的本地浏览器中", "non-select-sql-warning": "当前语句可能是非 SELECT SQL,这将导致数据库模式或数据变化。", "product-hunt": "🚀🚀🚀 我们刚在 Product Hunt 发布, 来给我们点个赞! 🚀🚀🚀", - "quota-overflow": "我们的 OpenAI 额度已满,正在申请额外的额度。请稍候再来。", - "use-my-key": "用我自己的 Key" + "quota-overflow": "我们的 OpenAI 额度已用完,请提供您自己的 OpenAI API key。", + "use-my-key": "提供我自己的 key" } } From d8f1e99192020d5f1632acf4956e52e9776412db Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 28 Apr 2023 17:14:47 +0800 Subject: [PATCH 032/237] refactor: move conversation list into component --- src/components/ConnectionSidebar.tsx | 103 +--------------- src/components/Sidebar/ConversationList.tsx | 123 ++++++++++++++++++++ 2 files changed, 127 insertions(+), 99 deletions(-) create mode 100644 src/components/Sidebar/ConversationList.tsx diff --git a/src/components/ConnectionSidebar.tsx b/src/components/ConnectionSidebar.tsx index 0ffd70e9..e01749bc 100644 --- a/src/components/ConnectionSidebar.tsx +++ b/src/components/ConnectionSidebar.tsx @@ -2,17 +2,16 @@ import { Drawer } from "@mui/material"; import { head } from "lodash-es"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useConversationStore, useConnectionStore, useLayoutStore, ResponsiveWidth } from "@/store"; -import { Conversation, Connection } from "@/types"; +import { useConnectionStore, useLayoutStore, ResponsiveWidth } from "@/store"; +import { Connection } from "@/types"; import Select from "./kit/Select"; import Tooltip from "./kit/Tooltip"; -import Dropdown, { DropdownItem } from "./kit/Dropdown"; import Icon from "./Icon"; import EngineIcon from "./EngineIcon"; import DarkModeSwitch from "./DarkModeSwitch"; import CreateConnectionModal from "./CreateConnectionModal"; import SettingModal from "./SettingModal"; -import UpdateConversationModal from "./UpdateConversationModal"; +import ConversationList from "./Sidebar/ConversationList"; interface State { showCreateConnectionModal: boolean; @@ -24,23 +23,16 @@ const ConnectionSidebar = () => { const { t } = useTranslation(); const layoutStore = useLayoutStore(); const connectionStore = useConnectionStore(); - const conversationStore = useConversationStore(); const [state, setState] = useState({ showCreateConnectionModal: false, showSettingModal: false, showUpdateConversationModal: false, }); const [editConnectionModalContext, setEditConnectionModalContext] = useState(); - const [updateConversationModalContext, setUpdateConversationModalContext] = useState(); const [isRequestingDatabase, setIsRequestingDatabase] = useState(false); const connectionList = connectionStore.connectionList; const currentConnectionCtx = connectionStore.currentConnectionCtx; const databaseList = connectionStore.databaseList.filter((database) => database.connectionId === currentConnectionCtx?.connection.id); - const conversationList = conversationStore.conversationList.filter( - (conversation) => - conversation.connectionId === currentConnectionCtx?.connection.id && - conversation.databaseName === currentConnectionCtx?.database?.name - ); useEffect(() => { const handleWindowResize = () => { @@ -87,13 +79,6 @@ const ConnectionSidebar = () => { }); }; - const toggleUpdateConversationModal = (show = true) => { - setState({ - ...state, - showUpdateConversationModal: show, - }); - }; - const handleConnectionSelect = async (connection: Connection) => { const databaseList = await connectionStore.getOrFetchDatabaseList(connection); connectionStore.setCurrentConnectionCtx({ @@ -123,36 +108,6 @@ const ConnectionSidebar = () => { }); }; - const handleCreateConversation = () => { - if (!currentConnectionCtx) { - conversationStore.createConversation(); - } else { - conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name); - } - }; - - const handleConversationSelect = (conversation: Conversation) => { - conversationStore.setCurrentConversationId(conversation.id); - if (layoutStore.isMobileView) { - layoutStore.toggleSidebar(false); - } - }; - - const handleEditConversation = (conversation: Conversation) => { - setUpdateConversationModalContext(conversation); - setState({ - ...state, - showUpdateConversationModal: true, - }); - }; - - const handleDeleteConversation = (conversation: Conversation) => { - conversationStore.clearConversation((item) => item.id !== conversation.id); - if (conversationStore.currentConversationId === conversation.id) { - conversationStore.setCurrentConversationId(undefined); - } - }; - return ( <> { />
)} - {conversationList.map((conversation) => ( -
handleConversationSelect(conversation)} - > - {conversation.id === conversationStore.currentConversationId ? ( - - ) : ( - - )} - {conversation.title || "SQL Chat"} - - - - } - > -
- handleEditConversation(conversation)} - > - - {t("common.edit")} - - handleDeleteConversation(conversation)} - > - - {t("common.delete")} - -
-
-
- ))} - +
{ )} {state.showSettingModal && toggleSettingModal(false)} />} - - {updateConversationModalContext && state.showUpdateConversationModal && ( - toggleUpdateConversationModal(false)} conversation={updateConversationModalContext} /> - )} ); }; diff --git a/src/components/Sidebar/ConversationList.tsx b/src/components/Sidebar/ConversationList.tsx new file mode 100644 index 00000000..eb99b386 --- /dev/null +++ b/src/components/Sidebar/ConversationList.tsx @@ -0,0 +1,123 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useConversationStore, useConnectionStore, useLayoutStore } from "@/store"; +import { Conversation } from "@/types"; +import Dropdown, { DropdownItem } from "../kit/Dropdown"; +import Icon from "../Icon"; +import UpdateConversationModal from "../UpdateConversationModal"; + +interface State { + showUpdateConversationModal: boolean; +} + +const ConversationList = () => { + const { t } = useTranslation(); + const layoutStore = useLayoutStore(); + const connectionStore = useConnectionStore(); + const conversationStore = useConversationStore(); + const [state, setState] = useState({ + showUpdateConversationModal: false, + }); + const [updateConversationModalContext, setUpdateConversationModalContext] = useState(); + const currentConnectionCtx = connectionStore.currentConnectionCtx; + const conversationList = conversationStore.conversationList.filter( + (conversation) => + conversation.connectionId === currentConnectionCtx?.connection.id && + conversation.databaseName === currentConnectionCtx?.database?.name + ); + + const toggleUpdateConversationModal = (show = true) => { + setState({ + ...state, + showUpdateConversationModal: show, + }); + }; + + const handleCreateConversation = () => { + if (!currentConnectionCtx) { + conversationStore.createConversation(); + } else { + conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name); + } + }; + + const handleConversationSelect = (conversation: Conversation) => { + conversationStore.setCurrentConversationId(conversation.id); + if (layoutStore.isMobileView) { + layoutStore.toggleSidebar(false); + } + }; + + const handleEditConversation = (conversation: Conversation) => { + setUpdateConversationModalContext(conversation); + setState({ + ...state, + showUpdateConversationModal: true, + }); + }; + + const handleDeleteConversation = (conversation: Conversation) => { + conversationStore.clearConversation((item) => item.id !== conversation.id); + if (conversationStore.currentConversationId === conversation.id) { + conversationStore.setCurrentConversationId(undefined); + } + }; + + return ( + <> + {conversationList.map((conversation) => ( +
handleConversationSelect(conversation)} + > + {conversation.id === conversationStore.currentConversationId ? ( + + ) : ( + + )} + {conversation.title || "SQL Chat"} + + + + } + > +
+ handleEditConversation(conversation)} + > + + {t("common.edit")} + + handleDeleteConversation(conversation)} + > + + {t("common.delete")} + +
+
+
+ ))} + + + {updateConversationModalContext && state.showUpdateConversationModal && ( + toggleUpdateConversationModal(false)} conversation={updateConversationModalContext} /> + )} + + ); +}; + +export default ConversationList; From 74754e147681c7177deaccf73de50400eba657c5 Mon Sep 17 00:00:00 2001 From: steven Date: Fri, 28 Apr 2023 17:20:56 +0800 Subject: [PATCH 033/237] refactor: move connection list into component --- src/components/ConnectionSidebar.tsx | 77 +----------------- src/components/Sidebar/ConnectionList.tsx | 98 +++++++++++++++++++++++ 2 files changed, 100 insertions(+), 75 deletions(-) create mode 100644 src/components/Sidebar/ConnectionList.tsx diff --git a/src/components/ConnectionSidebar.tsx b/src/components/ConnectionSidebar.tsx index e01749bc..569ce58e 100644 --- a/src/components/ConnectionSidebar.tsx +++ b/src/components/ConnectionSidebar.tsx @@ -1,22 +1,17 @@ import { Drawer } from "@mui/material"; -import { head } from "lodash-es"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useConnectionStore, useLayoutStore, ResponsiveWidth } from "@/store"; -import { Connection } from "@/types"; import Select from "./kit/Select"; import Tooltip from "./kit/Tooltip"; import Icon from "./Icon"; -import EngineIcon from "./EngineIcon"; import DarkModeSwitch from "./DarkModeSwitch"; -import CreateConnectionModal from "./CreateConnectionModal"; import SettingModal from "./SettingModal"; import ConversationList from "./Sidebar/ConversationList"; +import ConnectionList from "./Sidebar/ConnectionList"; interface State { - showCreateConnectionModal: boolean; showSettingModal: boolean; - showUpdateConversationModal: boolean; } const ConnectionSidebar = () => { @@ -24,13 +19,9 @@ const ConnectionSidebar = () => { const layoutStore = useLayoutStore(); const connectionStore = useConnectionStore(); const [state, setState] = useState({ - showCreateConnectionModal: false, showSettingModal: false, - showUpdateConversationModal: false, }); - const [editConnectionModalContext, setEditConnectionModalContext] = useState(); const [isRequestingDatabase, setIsRequestingDatabase] = useState(false); - const connectionList = connectionStore.connectionList; const currentConnectionCtx = connectionStore.currentConnectionCtx; const databaseList = connectionStore.databaseList.filter((database) => database.connectionId === currentConnectionCtx?.connection.id); @@ -64,14 +55,6 @@ const ConnectionSidebar = () => { } }, [currentConnectionCtx?.connection]); - const toggleCreateConnectionModal = (show = true) => { - setState({ - ...state, - showCreateConnectionModal: show, - }); - setEditConnectionModalContext(undefined); - }; - const toggleSettingModal = (show = true) => { setState({ ...state, @@ -79,22 +62,6 @@ const ConnectionSidebar = () => { }); }; - const handleConnectionSelect = async (connection: Connection) => { - const databaseList = await connectionStore.getOrFetchDatabaseList(connection); - connectionStore.setCurrentConnectionCtx({ - connection, - database: head(databaseList), - }); - }; - - const handleEditConnection = (connection: Connection) => { - setState({ - ...state, - showCreateConnectionModal: true, - }); - setEditConnectionModalContext(connection); - }; - const handleDatabaseNameSelect = async (databaseName: string) => { if (!currentConnectionCtx?.connection) { return; @@ -120,43 +87,7 @@ const ConnectionSidebar = () => {
- - {connectionList.map((connection) => ( - - - - ))} - - - +
@@ -237,10 +168,6 @@ const ConnectionSidebar = () => {
- {state.showCreateConnectionModal && ( - toggleCreateConnectionModal(false)} /> - )} - {state.showSettingModal && toggleSettingModal(false)} />} ); diff --git a/src/components/Sidebar/ConnectionList.tsx b/src/components/Sidebar/ConnectionList.tsx new file mode 100644 index 00000000..ec2afb4d --- /dev/null +++ b/src/components/Sidebar/ConnectionList.tsx @@ -0,0 +1,98 @@ +import { head } from "lodash-es"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useConnectionStore } from "@/store"; +import { Connection } from "@/types"; +import Tooltip from "../kit/Tooltip"; +import Icon from "../Icon"; +import EngineIcon from "../EngineIcon"; +import CreateConnectionModal from "../CreateConnectionModal"; + +interface State { + showCreateConnectionModal: boolean; + showSettingModal: boolean; + showUpdateConversationModal: boolean; +} + +const ConnectionList = () => { + const { t } = useTranslation(); + const connectionStore = useConnectionStore(); + const [state, setState] = useState({ + showCreateConnectionModal: false, + showSettingModal: false, + showUpdateConversationModal: false, + }); + const [editConnectionModalContext, setEditConnectionModalContext] = useState(); + const connectionList = connectionStore.connectionList; + const currentConnectionCtx = connectionStore.currentConnectionCtx; + + const toggleCreateConnectionModal = (show = true) => { + setState({ + ...state, + showCreateConnectionModal: show, + }); + setEditConnectionModalContext(undefined); + }; + + const handleConnectionSelect = async (connection: Connection) => { + const databaseList = await connectionStore.getOrFetchDatabaseList(connection); + connectionStore.setCurrentConnectionCtx({ + connection, + database: head(databaseList), + }); + }; + + const handleEditConnection = (connection: Connection) => { + setState({ + ...state, + showCreateConnectionModal: true, + }); + setEditConnectionModalContext(connection); + }; + + return ( + <> + + {connectionList.map((connection) => ( + + + + ))} + + + + + {state.showCreateConnectionModal && ( + toggleCreateConnectionModal(false)} /> + )} + + ); +}; + +export default ConnectionList; From ea7a48a26bfcd04d9da1f36deb8577759a55004f Mon Sep 17 00:00:00 2001 From: Tianzhou Chen Date: Sat, 29 Apr 2023 22:49:07 +0800 Subject: [PATCH 034/237] chore: use prettier for project and apply to all files --- .vscode/settings.json | 36 +- package.json | 1 + pnpm-lock.yaml | 345 ++++++++---------- postcss.config.js | 2 +- src/components/ClearConversationButton.tsx | 11 +- .../ClearConversationConfirmModal.tsx | 8 +- src/components/ClearDataButton.tsx | 14 +- src/components/ClearDataConfirmModal.tsx | 5 +- src/components/CodeBlock.tsx | 11 +- src/components/ConnectionSidebar.tsx | 34 +- src/components/ConversationView/Header.tsx | 18 +- .../ConversationView/MessageTextarea.tsx | 16 +- .../ConversationView/MessageView.tsx | 45 ++- .../ConversationView/ThreeDotsLoader.tsx | 14 +- src/components/ConversationView/index.tsx | 75 +++- src/components/CreateConnectionModal.tsx | 129 +++++-- src/components/DataStorageBanner.tsx | 10 +- src/components/EmptyView.tsx | 36 +- src/components/EngineIcon.tsx | 2 +- .../ExecutionView/DataTableView.tsx | 4 +- .../ExecutionView/ExecutionWarningBanner.tsx | 6 +- .../ExecutionView/NotificationView.tsx | 8 +- src/components/GitHubStarBadge.tsx | 21 +- src/components/LocaleSelector.tsx | 9 +- src/components/OpenAIApiConfigView.tsx | 8 +- src/components/ProductHuntBanner.tsx | 10 +- src/components/QueryDrawer.tsx | 49 ++- src/components/QuotaOverflowBanner.tsx | 19 +- src/components/SettingModal.tsx | 4 +- src/components/Sidebar/ConnectionList.tsx | 25 +- src/components/Sidebar/ConversationList.tsx | 26 +- src/components/ThemeSelector.tsx | 9 +- src/components/ThemeSwitch.tsx | 5 +- src/components/UpdateConversationModal.tsx | 28 +- src/components/kit/Modal.tsx | 8 +- src/components/kit/Popover.tsx | 4 +- src/components/kit/Select.tsx | 28 +- src/components/kit/TextField.tsx | 9 +- src/lib/connectors/index.ts | 11 +- src/lib/connectors/mssql/index.ts | 49 ++- src/lib/connectors/mysql/index.ts | 43 ++- src/lib/connectors/postgres/index.ts | 39 +- src/pages/_app.tsx | 5 +- src/pages/api/chat.ts | 10 +- src/pages/api/connection/db_schema.ts | 14 +- src/pages/api/usage.ts | 5 +- src/pages/index.tsx | 51 ++- src/store/assistant.ts | 12 +- src/store/connection.ts | 88 ++++- src/store/conversation.ts | 30 +- src/store/message.ts | 10 +- src/store/setting.ts | 3 +- src/utils/execution.ts | 4 +- src/utils/openai.ts | 3 +- tailwind.config.js | 5 +- 55 files changed, 1040 insertions(+), 434 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4e713441..c68b19b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,33 @@ { - "i18n-ally.localesPaths": [ - "src/locales" - ] -} \ No newline at end of file + "i18n-ally.localesPaths": ["src/locales"], + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file" + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file" + } +} diff --git a/package.json b/package.json index 92727f6a..79161f46 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "mysql2": "^3.2.0", "pg": "^8.10.0", "postcss": "^8.4.20", + "prettier": "^2.8.8", "prisma": "^4.13.0", "react-syntax-highlighter": "^15.5.0", "tailwindcss": "^3.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64902549..a586505e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -159,6 +159,9 @@ devDependencies: postcss: specifier: ^8.4.20 version: 8.4.21 + prettier: + specifier: ^2.8.8 + version: 2.8.8 prisma: specifier: ^4.13.0 version: 4.13.0 @@ -883,6 +886,123 @@ packages: glob: 7.1.7 dev: true + /@next/swc-android-arm-eabi@13.2.4: + resolution: {integrity: sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@next/swc-android-arm64@13.2.4: + resolution: {integrity: sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-arm64@13.2.4: + resolution: {integrity: sha512-S6vBl+OrInP47TM3LlYx65betocKUUlTZDDKzTiRDbsRESeyIkBtZ6Qi5uT2zQs4imqllJznVjFd1bXLx3Aa6A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-x64@13.2.4: + resolution: {integrity: sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-freebsd-x64@13.2.4: + resolution: {integrity: sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm-gnueabihf@13.2.4: + resolution: {integrity: sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-gnu@13.2.4: + resolution: {integrity: sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-musl@13.2.4: + resolution: {integrity: sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-gnu@13.2.4: + resolution: {integrity: sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-musl@13.2.4: + resolution: {integrity: sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-arm64-msvc@13.2.4: + resolution: {integrity: sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-ia32-msvc@13.2.4: + resolution: {integrity: sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-x64-msvc@13.2.4: + resolution: {integrity: sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1966,7 +2086,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: registry.npmmirror.com/fsevents@2.3.2 + fsevents: 2.3.2 dev: true /client-only@0.0.1: @@ -2793,6 +2913,14 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} @@ -4016,19 +4144,19 @@ packages: react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(react@18.2.0) optionalDependencies: - '@next/swc-android-arm-eabi': registry.npmmirror.com/@next/swc-android-arm-eabi@13.2.4 - '@next/swc-android-arm64': registry.npmmirror.com/@next/swc-android-arm64@13.2.4 - '@next/swc-darwin-arm64': registry.npmmirror.com/@next/swc-darwin-arm64@13.2.4 - '@next/swc-darwin-x64': registry.npmmirror.com/@next/swc-darwin-x64@13.2.4 - '@next/swc-freebsd-x64': registry.npmmirror.com/@next/swc-freebsd-x64@13.2.4 - '@next/swc-linux-arm-gnueabihf': registry.npmmirror.com/@next/swc-linux-arm-gnueabihf@13.2.4 - '@next/swc-linux-arm64-gnu': registry.npmmirror.com/@next/swc-linux-arm64-gnu@13.2.4 - '@next/swc-linux-arm64-musl': registry.npmmirror.com/@next/swc-linux-arm64-musl@13.2.4 - '@next/swc-linux-x64-gnu': registry.npmmirror.com/@next/swc-linux-x64-gnu@13.2.4 - '@next/swc-linux-x64-musl': registry.npmmirror.com/@next/swc-linux-x64-musl@13.2.4 - '@next/swc-win32-arm64-msvc': registry.npmmirror.com/@next/swc-win32-arm64-msvc@13.2.4 - '@next/swc-win32-ia32-msvc': registry.npmmirror.com/@next/swc-win32-ia32-msvc@13.2.4 - '@next/swc-win32-x64-msvc': registry.npmmirror.com/@next/swc-win32-x64-msvc@13.2.4 + '@next/swc-android-arm-eabi': 13.2.4 + '@next/swc-android-arm64': 13.2.4 + '@next/swc-darwin-arm64': 13.2.4 + '@next/swc-darwin-x64': 13.2.4 + '@next/swc-freebsd-x64': 13.2.4 + '@next/swc-linux-arm-gnueabihf': 13.2.4 + '@next/swc-linux-arm64-gnu': 13.2.4 + '@next/swc-linux-arm64-musl': 13.2.4 + '@next/swc-linux-x64-gnu': 13.2.4 + '@next/swc-linux-x64-musl': 13.2.4 + '@next/swc-win32-arm64-msvc': 13.2.4 + '@next/swc-win32-ia32-msvc': 13.2.4 + '@next/swc-win32-x64-msvc': 13.2.4 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -4379,6 +4507,12 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /prisma@4.13.0: resolution: {integrity: sha512-L9mqjnSmvWIRCYJ9mQkwCtj4+JDYYTdhoyo8hlsHNDXaZLh/b4hR0IoKIBbTKxZuyHQzLopb/+0Rvb69uGV7uA==} engines: {node: '>=14.17'} @@ -5574,7 +5708,7 @@ packages: dev: false registry.npmmirror.com/@babel/runtime@7.21.0: - resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz} + resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz} name: '@babel/runtime' version: 7.21.0 engines: {node: '>=6.9.0'} @@ -5582,155 +5716,8 @@ packages: regenerator-runtime: registry.npmmirror.com/regenerator-runtime@0.13.11 dev: false - registry.npmmirror.com/@next/swc-android-arm-eabi@13.2.4: - resolution: {integrity: sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz} - name: '@next/swc-android-arm-eabi' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-android-arm64@13.2.4: - resolution: {integrity: sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz} - name: '@next/swc-android-arm64' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-darwin-arm64@13.2.4: - resolution: {integrity: sha512-S6vBl+OrInP47TM3LlYx65betocKUUlTZDDKzTiRDbsRESeyIkBtZ6Qi5uT2zQs4imqllJznVjFd1bXLx3Aa6A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.4.tgz} - name: '@next/swc-darwin-arm64' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-darwin-x64@13.2.4: - resolution: {integrity: sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.4.tgz} - name: '@next/swc-darwin-x64' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-freebsd-x64@13.2.4: - resolution: {integrity: sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz} - name: '@next/swc-freebsd-x64' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-linux-arm-gnueabihf@13.2.4: - resolution: {integrity: sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz} - name: '@next/swc-linux-arm-gnueabihf' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-linux-arm64-gnu@13.2.4: - resolution: {integrity: sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.4.tgz} - name: '@next/swc-linux-arm64-gnu' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-linux-arm64-musl@13.2.4: - resolution: {integrity: sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.4.tgz} - name: '@next/swc-linux-arm64-musl' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-linux-x64-gnu@13.2.4: - resolution: {integrity: sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.4.tgz} - name: '@next/swc-linux-x64-gnu' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-linux-x64-musl@13.2.4: - resolution: {integrity: sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.4.tgz} - name: '@next/swc-linux-x64-musl' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-win32-arm64-msvc@13.2.4: - resolution: {integrity: sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.4.tgz} - name: '@next/swc-win32-arm64-msvc' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-win32-ia32-msvc@13.2.4: - resolution: {integrity: sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.4.tgz} - name: '@next/swc-win32-ia32-msvc' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - registry.npmmirror.com/@next/swc-win32-x64-msvc@13.2.4: - resolution: {integrity: sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.4.tgz} - name: '@next/swc-win32-x64-msvc' - version: 13.2.4 - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - registry.npmmirror.com/@radix-ui/number@1.0.0: - resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/number/-/number-1.0.0.tgz} + resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/number/-/number-1.0.0.tgz} name: '@radix-ui/number' version: 1.0.0 dependencies: @@ -5738,7 +5725,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/primitive@1.0.0: - resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.0.tgz} + resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.0.tgz} name: '@radix-ui/primitive' version: 1.0.0 dependencies: @@ -5746,7 +5733,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-compose-refs@1.0.0(react@18.2.0): - resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz} + resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz} id: registry.npmmirror.com/@radix-ui/react-compose-refs/1.0.0 name: '@radix-ui/react-compose-refs' version: 1.0.0 @@ -5758,7 +5745,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-context@1.0.0(react@18.2.0): - resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.0.tgz} + resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.0.tgz} id: registry.npmmirror.com/@radix-ui/react-context/1.0.0 name: '@radix-ui/react-context' version: 1.0.0 @@ -5770,7 +5757,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-direction@1.0.0(react@18.2.0): - resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz} + resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz} id: registry.npmmirror.com/@radix-ui/react-direction/1.0.0 name: '@radix-ui/react-direction' version: 1.0.0 @@ -5782,7 +5769,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz} + resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz} id: registry.npmmirror.com/@radix-ui/react-presence/1.0.0 name: '@radix-ui/react-presence' version: 1.0.0 @@ -5798,7 +5785,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-primitive@1.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz} + resolution: {integrity: sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz} id: registry.npmmirror.com/@radix-ui/react-primitive/1.0.2 name: '@radix-ui/react-primitive' version: 1.0.2 @@ -5813,7 +5800,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-scroll-area@1.0.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-sBX9j8Q+0/jReNObEAveKIGXJtk3xUoSIx4cMKygGtO128QJyVDn01XNOFsyvihKDCTcu7SINzQ2jPAZEhIQtw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.3.tgz} + resolution: {integrity: sha512-sBX9j8Q+0/jReNObEAveKIGXJtk3xUoSIx4cMKygGtO128QJyVDn01XNOFsyvihKDCTcu7SINzQ2jPAZEhIQtw==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.3.tgz} id: registry.npmmirror.com/@radix-ui/react-scroll-area/1.0.3 name: '@radix-ui/react-scroll-area' version: 1.0.3 @@ -5836,7 +5823,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-slot@1.0.1(react@18.2.0): - resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.1.tgz} + resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.1.tgz} id: registry.npmmirror.com/@radix-ui/react-slot/1.0.1 name: '@radix-ui/react-slot' version: 1.0.1 @@ -5849,7 +5836,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0): - resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz} + resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz} id: registry.npmmirror.com/@radix-ui/react-use-callback-ref/1.0.0 name: '@radix-ui/react-use-callback-ref' version: 1.0.0 @@ -5861,7 +5848,7 @@ packages: dev: false registry.npmmirror.com/@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0): - resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz} + resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz} id: registry.npmmirror.com/@radix-ui/react-use-layout-effect/1.0.0 name: '@radix-ui/react-use-layout-effect' version: 1.0.0 @@ -5872,18 +5859,8 @@ packages: react: 18.2.0 dev: false - registry.npmmirror.com/fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz} - name: fsevents - version: 2.3.2 - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - registry.npmmirror.com/regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz} + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz} name: regenerator-runtime version: 0.13.11 dev: false diff --git a/postcss.config.js b/postcss.config.js index 33ad091d..12a703d9 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -3,4 +3,4 @@ module.exports = { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/src/components/ClearConversationButton.tsx b/src/components/ClearConversationButton.tsx index 8f941e01..af047f74 100644 --- a/src/components/ClearConversationButton.tsx +++ b/src/components/ClearConversationButton.tsx @@ -7,7 +7,10 @@ const ClearConversationButton = () => { const conversationStore = useConversationStore(); const messageStore = useMessageStore(); const [showConfirmModal, setShowConfirmModal] = useState(false); - const messageList = messageStore.messageList.filter((message) => message.conversationId === conversationStore.currentConversationId); + const messageList = messageStore.messageList.filter( + (message) => + message.conversationId === conversationStore.currentConversationId + ); return ( <> @@ -19,7 +22,11 @@ const ClearConversationButton = () => { - {showConfirmModal && setShowConfirmModal(false)} />} + {showConfirmModal && ( + setShowConfirmModal(false)} + /> + )} ); }; diff --git a/src/components/ClearConversationConfirmModal.tsx b/src/components/ClearConversationConfirmModal.tsx index d7be4836..57a913b7 100644 --- a/src/components/ClearConversationConfirmModal.tsx +++ b/src/components/ClearConversationConfirmModal.tsx @@ -13,7 +13,9 @@ const ClearConversationConfirmModal = (props: Props) => { const messageStore = useMessageStore(); const handleClearMessages = () => { - messageStore.clearMessage((item) => item.conversationId !== conversationStore.currentConversationId); + messageStore.clearMessage( + (item) => item.conversationId !== conversationStore.currentConversationId + ); close(); }; @@ -21,7 +23,9 @@ const ClearConversationConfirmModal = (props: Props) => {
-

Are you sure to clear the messages in current conversation?

+

+ Are you sure to clear the messages in current conversation? +

- {showClearDataConfirmModal && setShowClearDataConfirmModal(false)} />} + {showClearDataConfirmModal && ( + setShowClearDataConfirmModal(false)} + /> + )} ); }; diff --git a/src/components/ClearDataConfirmModal.tsx b/src/components/ClearDataConfirmModal.tsx index cebe873a..dad5a0e9 100644 --- a/src/components/ClearDataConfirmModal.tsx +++ b/src/components/ClearDataConfirmModal.tsx @@ -23,7 +23,10 @@ const ClearDataConfirmModal = (props: Props) => {
-

SQL Chat saves all your data in your local browser. Are you sure to clear all of them?

+

+ SQL Chat saves all your data in your local browser. Are you sure to + clear all of them? +

- + {value}
diff --git a/src/components/ConnectionSidebar.tsx b/src/components/ConnectionSidebar.tsx index 569ce58e..ff3a2db2 100644 --- a/src/components/ConnectionSidebar.tsx +++ b/src/components/ConnectionSidebar.tsx @@ -21,9 +21,12 @@ const ConnectionSidebar = () => { const [state, setState] = useState({ showSettingModal: false, }); - const [isRequestingDatabase, setIsRequestingDatabase] = useState(false); + const [isRequestingDatabase, setIsRequestingDatabase] = + useState(false); const currentConnectionCtx = connectionStore.currentConnectionCtx; - const databaseList = connectionStore.databaseList.filter((database) => database.connectionId === currentConnectionCtx?.connection.id); + const databaseList = connectionStore.databaseList.filter( + (database) => database.connectionId === currentConnectionCtx?.connection.id + ); useEffect(() => { const handleWindowResize = () => { @@ -47,9 +50,11 @@ const ConnectionSidebar = () => { useEffect(() => { if (currentConnectionCtx?.connection) { setIsRequestingDatabase(true); - connectionStore.getOrFetchDatabaseList(currentConnectionCtx.connection).finally(() => { - setIsRequestingDatabase(false); - }); + connectionStore + .getOrFetchDatabaseList(currentConnectionCtx.connection) + .finally(() => { + setIsRequestingDatabase(false); + }); } else { setIsRequestingDatabase(false); } @@ -67,8 +72,12 @@ const ConnectionSidebar = () => { return; } - const databaseList = await connectionStore.getOrFetchDatabaseList(currentConnectionCtx.connection); - const database = databaseList.find((database) => database.name === databaseName); + const databaseList = await connectionStore.getOrFetchDatabaseList( + currentConnectionCtx.connection + ); + const database = databaseList.find( + (database) => database.name === databaseName + ); connectionStore.setCurrentConnectionCtx({ connection: currentConnectionCtx.connection, database: database, @@ -107,7 +116,8 @@ const ConnectionSidebar = () => {
{isRequestingDatabase && (
- {t("common.loading")} + {" "} + {t("common.loading")}
)} {databaseList.length > 0 && ( @@ -121,7 +131,9 @@ const ConnectionSidebar = () => { value: database.name, }; })} - onValueChange={(databaseName) => handleDatabaseNameSelect(databaseName)} + onValueChange={(databaseName) => + handleDatabaseNameSelect(databaseName) + } placeholder={t("connection.select-database") || ""} />
@@ -168,7 +180,9 @@ const ConnectionSidebar = () => {
- {state.showSettingModal && toggleSettingModal(false)} />} + {state.showSettingModal && ( + toggleSettingModal(false)} /> + )} ); }; diff --git a/src/components/ConversationView/Header.tsx b/src/components/ConversationView/Header.tsx index 0793c86b..8b02d0bc 100644 --- a/src/components/ConversationView/Header.tsx +++ b/src/components/ConversationView/Header.tsx @@ -14,7 +14,9 @@ const Header = (props: Props) => { const conversationStore = useConversationStore(); const isDarkMode = useDarkMode(); const currentConversationId = conversationStore.currentConversationId; - const title = conversationStore.getConversationById(currentConversationId)?.title || "SQL Chat"; + const title = + conversationStore.getConversationById(currentConversationId)?.title || + "SQL Chat"; useEffect(() => { document.title = `${title}`; @@ -36,14 +38,24 @@ const Header = (props: Props) => { {title}
- {title} + + {title} +
diff --git a/src/components/ConversationView/MessageTextarea.tsx b/src/components/ConversationView/MessageTextarea.tsx index faa6340a..d7833043 100644 --- a/src/components/ConversationView/MessageTextarea.tsx +++ b/src/components/ConversationView/MessageTextarea.tsx @@ -2,7 +2,12 @@ import { useEffect, useRef, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import TextareaAutosize from "react-textarea-autosize"; -import { useConversationStore, useConnectionStore, useMessageStore, useUserStore } from "@/store"; +import { + useConversationStore, + useConnectionStore, + useMessageStore, + useUserStore, +} from "@/store"; import { CreatorRole } from "@/types"; import { generateUUID } from "@/utils"; import Icon from "../Icon"; @@ -34,13 +39,18 @@ const MessageTextarea = (props: Props) => { }; const handleSend = async () => { - let conversation = conversationStore.getConversationById(conversationStore.currentConversationId); + let conversation = conversationStore.getConversationById( + conversationStore.currentConversationId + ); if (!conversation) { const currentConnectionCtx = connectionStore.currentConnectionCtx; if (!currentConnectionCtx) { conversation = conversationStore.createConversation(); } else { - conversation = conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name); + conversation = conversationStore.createConversation( + currentConnectionCtx.connection.id, + currentConnectionCtx.database?.name + ); } } if (!value) { diff --git a/src/components/ConversationView/MessageView.tsx b/src/components/ConversationView/MessageView.tsx index 6447a749..c97067b4 100644 --- a/src/components/ConversationView/MessageView.tsx +++ b/src/components/ConversationView/MessageView.tsx @@ -4,7 +4,13 @@ import { useTranslation } from "react-i18next"; import { toast } from "react-hot-toast"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; -import { useConversationStore, useConnectionStore, useMessageStore, useUserStore, useSettingStore } from "@/store"; +import { + useConversationStore, + useConnectionStore, + useMessageStore, + useUserStore, + useSettingStore, +} from "@/store"; import { Message } from "@/types"; import Dropdown, { DropdownItem } from "../kit/Dropdown"; import Icon from "../Icon"; @@ -25,7 +31,10 @@ const MessageView = (props: Props) => { const connectionStore = useConnectionStore(); const messageStore = useMessageStore(); const isCurrentUser = message.creatorId === userStore.currentUser.id; - const connection = connectionStore.getConnectionById(conversationStore.getConversationById(message.conversationId)?.connectionId || ""); + const connection = connectionStore.getConnectionById( + conversationStore.getConversationById(message.conversationId) + ?.connectionId || "" + ); const copyMessage = () => { navigator.clipboard.writeText(message.content); @@ -83,9 +92,16 @@ const MessageView = (props: Props) => { <>
{connection ? ( - + ) : ( - + )}
{message.status === "LOADING" && message.content === "" ? ( @@ -97,20 +113,29 @@ const MessageView = (props: Props) => {
+
                           
                         
@@ -124,7 +149,9 @@ const MessageView = (props: Props) => { {message.content}
- {dayjs(message.createdAt).locale(settingStore.setting.locale).format("lll")} + {dayjs(message.createdAt) + .locale(settingStore.setting.locale) + .format("lll")}
diff --git a/src/components/ConversationView/ThreeDotsLoader.tsx b/src/components/ConversationView/ThreeDotsLoader.tsx index 4cd4d689..2f4fb236 100644 --- a/src/components/ConversationView/ThreeDotsLoader.tsx +++ b/src/components/ConversationView/ThreeDotsLoader.tsx @@ -10,7 +10,10 @@ const ThreeDotsLoader = () => { const theme = settingStore.setting.theme; let appearance = theme; if (theme === "system") { - if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { appearance = "dark"; } else { appearance = "light"; @@ -24,7 +27,14 @@ const ThreeDotsLoader = () => { } }, [settingStore.setting.theme]); - return ; + return ( + + ); }; export default ThreeDotsLoader; diff --git a/src/components/ConversationView/index.tsx b/src/components/ConversationView/index.tsx index 7c4014a4..dd5f1c9b 100644 --- a/src/components/ConversationView/index.tsx +++ b/src/components/ConversationView/index.tsx @@ -35,9 +35,13 @@ const ConversationView = () => { const [isStickyAtBottom, setIsStickyAtBottom] = useState(true); const [showHeaderShadow, setShowHeaderShadow] = useState(false); const conversationViewRef = useRef(null); - const currentConversation = conversationStore.getConversationById(conversationStore.currentConversationId); + const currentConversation = conversationStore.getConversationById( + conversationStore.currentConversationId + ); const messageList = currentConversation - ? messageStore.messageList.filter((message) => message.conversationId === currentConversation.id) + ? messageStore.messageList.filter( + (message) => message.conversationId === currentConversation.id + ) : []; const lastMessage = last(messageList); @@ -63,13 +67,21 @@ const ConversationView = () => { } setShowHeaderShadow((conversationViewRef.current?.scrollTop || 0) > 0); setIsStickyAtBottom( - conversationViewRef.current.scrollTop + conversationViewRef.current.clientHeight >= conversationViewRef.current.scrollHeight + conversationViewRef.current.scrollTop + + conversationViewRef.current.clientHeight >= + conversationViewRef.current.scrollHeight ); }; - conversationViewRef.current?.addEventListener("scroll", handleConversationViewScroll); + conversationViewRef.current?.addEventListener( + "scroll", + handleConversationViewScroll + ); return () => { - conversationViewRef.current?.removeEventListener("scroll", handleConversationViewScroll); + conversationViewRef.current?.removeEventListener( + "scroll", + handleConversationViewScroll + ); }; }, []); @@ -77,7 +89,8 @@ const ConversationView = () => { if (!conversationViewRef.current) { return; } - conversationViewRef.current.scrollTop = conversationViewRef.current.scrollHeight; + conversationViewRef.current.scrollTop = + conversationViewRef.current.scrollHeight; }, [currentConversation, lastMessage?.id]); useEffect(() => { @@ -86,14 +99,17 @@ const ConversationView = () => { } if (lastMessage?.status === "LOADING" && isStickyAtBottom) { - conversationViewRef.current.scrollTop = conversationViewRef.current.scrollHeight; + conversationViewRef.current.scrollTop = + conversationViewRef.current.scrollHeight; } }, [lastMessage?.status, lastMessage?.content, isStickyAtBottom]); useEffect(() => { if ( - currentConversation?.connectionId === connectionStore.currentConnectionCtx?.connection.id && - currentConversation?.databaseName === connectionStore.currentConnectionCtx?.database?.name + currentConversation?.connectionId === + connectionStore.currentConnectionCtx?.connection.id && + currentConversation?.databaseName === + connectionStore.currentConnectionCtx?.database?.name ) { return; } @@ -101,14 +117,18 @@ const ConversationView = () => { // Auto select the first conversation when the current connection changes. const conversationList = conversationStore.conversationList.filter( (conversation) => - conversation.connectionId === connectionStore.currentConnectionCtx?.connection.id && - conversation.databaseName === connectionStore.currentConnectionCtx?.database?.name + conversation.connectionId === + connectionStore.currentConnectionCtx?.connection.id && + conversation.databaseName === + connectionStore.currentConnectionCtx?.database?.name ); conversationStore.setCurrentConversationId(head(conversationList)?.id); }, [currentConversation, connectionStore.currentConnectionCtx]); const sendMessageToCurrentConversation = async () => { - const currentConversation = conversationStore.getConversationById(conversationStore.getState().currentConversationId); + const currentConversation = conversationStore.getConversationById( + conversationStore.getState().currentConversationId + ); if (!currentConversation) { return; } @@ -116,8 +136,14 @@ const ConversationView = () => { return; } - const messageList = messageStore.getState().messageList.filter((message) => message.conversationId === currentConversation.id); - const promptGenerator = getPromptGeneratorOfAssistant(getAssistantById(currentConversation.assistantId)!); + const messageList = messageStore + .getState() + .messageList.filter( + (message) => message.conversationId === currentConversation.id + ); + const promptGenerator = getPromptGeneratorOfAssistant( + getAssistantById(currentConversation.assistantId)! + ); let prompt = promptGenerator(); let tokens = 0; @@ -135,7 +161,9 @@ const ConversationView = () => { if (connectionStore.currentConnectionCtx?.database) { let schema = ""; try { - const tables = await connectionStore.getOrFetchDatabaseSchema(connectionStore.currentConnectionCtx?.database); + const tables = await connectionStore.getOrFetchDatabaseSchema( + connectionStore.currentConnectionCtx?.database + ); for (const table of tables) { if (tokens < MAX_TOKENS / 2) { tokens += countTextTokens(schema + table.structure); @@ -187,7 +215,8 @@ const ConversationView = () => { if (!rawRes.ok) { console.error(rawRes); - let errorMessage = "Failed to request message, please check your network."; + let errorMessage = + "Failed to request message, please check your network."; try { const res = await rawRes.json(); errorMessage = res.error.message; @@ -253,14 +282,22 @@ const ConversationView = () => {
{messageList.length === 0 ? ( - + ) : ( - messageList.map((message) => ) + messageList.map((message) => ( + + )) )}
- +
); diff --git a/src/components/CreateConnectionModal.tsx b/src/components/CreateConnectionModal.tsx index f79e657f..03608d66 100644 --- a/src/components/CreateConnectionModal.tsx +++ b/src/components/CreateConnectionModal.tsx @@ -51,7 +51,8 @@ const CreateConnectionModal = (props: Props) => { const { t } = useTranslation(); const connectionStore = useConnectionStore(); const [connection, setConnection] = useState(defaultConnection); - const [showDeleteConnectionModal, setShowDeleteConnectionModal] = useState(false); + const [showDeleteConnectionModal, setShowDeleteConnectionModal] = + useState(false); const [sslType, setSSLType] = useState("none"); const [selectedSSLField, setSelectedSSLField] = useState("ca"); const [isRequesting, setIsRequesting] = useState(false); @@ -105,7 +106,11 @@ const CreateConnectionModal = (props: Props) => { } const file = files[0]; - if (file.type.startsWith("audio/") || file.type.startsWith("video/") || file.type.startsWith("image/")) { + if ( + file.type.startsWith("audio/") || + file.type.startsWith("video/") || + file.type.startsWith("image/") + ) { toast.error(`Invalid file type:${file.type}`); return; } @@ -177,7 +182,10 @@ const CreateConnectionModal = (props: Props) => { } // Set the created connection as the current connection. - const databaseList = await connectionStore.getOrFetchDatabaseList(connection, true); + const databaseList = await connectionStore.getOrFetchDatabaseList( + connection, + true + ); connectionStore.setCurrentConnectionCtx({ connection: connection, database: head(databaseList), @@ -203,11 +211,19 @@ const CreateConnectionModal = (props: Props) => { return ( <> - +
- +
- + {
setSelectedSSLField("ca")} > @@ -289,7 +337,8 @@ const CreateConnectionModal = (props: Props) => { <> setSelectedSSLField("key")} > @@ -297,7 +346,8 @@ const CreateConnectionModal = (props: Props) => { setSelectedSSLField("cert")} > @@ -311,18 +361,26 @@ const CreateConnectionModal = (props: Props) => { className="w-full border resize-none rounded-lg text-sm p-3" minRows={3} maxRows={3} - value={(connection.ssl && connection.ssl[selectedSSLField]) ?? ""} + value={ + (connection.ssl && connection.ssl[selectedSSLField]) ?? "" + } onChange={handleSSLValueChange} />
Input or
@@ -330,14 +388,18 @@ const CreateConnectionModal = (props: Props) => { )} {connection.engineType === Engine.MSSQL && (
- +
@@ -349,7 +411,10 @@ const CreateConnectionModal = (props: Props) => {
{isEditing && ( - )} @@ -358,8 +423,14 @@ const CreateConnectionModal = (props: Props) => { -
diff --git a/src/components/DataStorageBanner.tsx b/src/components/DataStorageBanner.tsx index 16288310..af8e3775 100644 --- a/src/components/DataStorageBanner.tsx +++ b/src/components/DataStorageBanner.tsx @@ -10,7 +10,10 @@ interface Props { const DataStorageBanner = (props: Props) => { const { className, alwaysShow } = props; const { t } = useTranslation(); - const [hideBanner, setHideBanner] = useLocalStorage("hide-local-storage-banner", false); + const [hideBanner, setHideBanner] = useLocalStorage( + "hide-local-storage-banner", + false + ); const show = alwaysShow || !hideBanner; return ( @@ -24,7 +27,10 @@ const DataStorageBanner = (props: Props) => { {t("banner.data-storage")} {!alwaysShow && ( - )} diff --git a/src/components/EmptyView.tsx b/src/components/EmptyView.tsx index 2514101c..8e457a9e 100644 --- a/src/components/EmptyView.tsx +++ b/src/components/EmptyView.tsx @@ -1,11 +1,19 @@ -import { useConversationStore, useConnectionStore, useMessageStore, useUserStore } from "@/store"; +import { + useConversationStore, + useConnectionStore, + useMessageStore, + useUserStore, +} from "@/store"; import { CreatorRole } from "@/types"; import { generateUUID } from "@/utils"; import useDarkMode from "@/hooks/useDarkmode"; import Icon from "./Icon"; // examples are used to show some examples to the user. -const examples = ["Give me an example schema about employee", "How to create a view in MySQL?"]; +const examples = [ + "Give me an example schema about employee", + "How to create a view in MySQL?", +]; interface Props { className?: string; @@ -21,13 +29,18 @@ const EmptyView = (props: Props) => { const isDarkMode = useDarkMode(); const handleExampleClick = async (content: string) => { - let conversation = conversationStore.getConversationById(conversationStore.currentConversationId); + let conversation = conversationStore.getConversationById( + conversationStore.currentConversationId + ); if (!conversation) { const currentConnectionCtx = connectionStore.currentConnectionCtx; if (!currentConnectionCtx) { conversation = conversationStore.createConversation(); } else { - conversation = conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name); + conversation = conversationStore.createConversation( + currentConnectionCtx.connection.id, + currentConnectionCtx.database?.name + ); } } @@ -44,9 +57,20 @@ const EmptyView = (props: Props) => { }; return ( -
+
- sql-chat-logo + sql-chat-logo
diff --git a/src/components/EngineIcon.tsx b/src/components/EngineIcon.tsx index 13ffc7c4..5c031d31 100644 --- a/src/components/EngineIcon.tsx +++ b/src/components/EngineIcon.tsx @@ -13,7 +13,7 @@ const EngineIcon = (props: Props) => { return ; } else if (engine === Engine.PostgreSQL) { return ; - }else if (engine === Engine.MSSQL) { + } else if (engine === Engine.MSSQL) { return ; } else { return ; diff --git a/src/components/ExecutionView/DataTableView.tsx b/src/components/ExecutionView/DataTableView.tsx index 4e5f6c14..754cd653 100644 --- a/src/components/ExecutionView/DataTableView.tsx +++ b/src/components/ExecutionView/DataTableView.tsx @@ -22,7 +22,9 @@ const DataTableView = (props: Props) => { return rawResults.length === 0 ? (
- {t("execution.message.no-data")} + + {t("execution.message.no-data")} +
) : ( { const { t } = useTranslation(); return ( -
+
{t("banner.non-select-sql-warning")} diff --git a/src/components/ExecutionView/NotificationView.tsx b/src/components/ExecutionView/NotificationView.tsx index b1c28539..c2e58dbd 100644 --- a/src/components/ExecutionView/NotificationView.tsx +++ b/src/components/ExecutionView/NotificationView.tsx @@ -6,7 +6,13 @@ interface Props { const NotificationView = (props: Props) => { const { message, style } = props; const additionalStyle = style === "error" ? "text-red-500" : "text-gray-500"; - return

{message}

; + return ( +

+ {message} +

+ ); }; export default NotificationView; diff --git a/src/components/GitHubStarBadge.tsx b/src/components/GitHubStarBadge.tsx index f641938b..11f57d4c 100644 --- a/src/components/GitHubStarBadge.tsx +++ b/src/components/GitHubStarBadge.tsx @@ -15,12 +15,15 @@ const GitHubStarBadge = (props: Props) => { const getRepoStarCount = async () => { let starCount = 0; try { - const { data } = await axios.get(`https://api.github.com/repos/sqlchat/sqlchat`, { - headers: { - Accept: "application/vnd.github.v3.star+json", - Authorization: "", - }, - }); + const { data } = await axios.get( + `https://api.github.com/repos/sqlchat/sqlchat`, + { + headers: { + Accept: "application/vnd.github.v3.star+json", + Authorization: "", + }, + } + ); starCount = data.stargazers_count as number; } catch (error) { // do nth @@ -47,7 +50,11 @@ const GitHubStarBadge = (props: Props) => { Star
- {isRequesting ? : stars} + {isRequesting ? ( + + ) : ( + stars + )}
); diff --git a/src/components/LocaleSelector.tsx b/src/components/LocaleSelector.tsx index 14ac7f30..1441ced5 100644 --- a/src/components/LocaleSelector.tsx +++ b/src/components/LocaleSelector.tsx @@ -46,7 +46,14 @@ const LocaleSelector = () => { settingStore.setLocale(locale); }; - return + ); }; export default LocaleSelector; diff --git a/src/components/OpenAIApiConfigView.tsx b/src/components/OpenAIApiConfigView.tsx index 7e70fd22..5c8813de 100644 --- a/src/components/OpenAIApiConfigView.tsx +++ b/src/components/OpenAIApiConfigView.tsx @@ -8,7 +8,9 @@ import TextField from "./kit/TextField"; const OpenAIApiConfigView = () => { const { t } = useTranslation(); const settingStore = useSettingStore(); - const [openAIApiConfig, setOpenAIApiConfig] = useState(settingStore.setting.openAIApiConfig); + const [openAIApiConfig, setOpenAIApiConfig] = useState( + settingStore.setting.openAIApiConfig + ); useDebounce( () => { @@ -27,7 +29,9 @@ const OpenAIApiConfigView = () => { return ( <> -

{t("setting.openai-api-configuration.self")}

+

+ {t("setting.openai-api-configuration.self")} +

diff --git a/src/components/ProductHuntBanner.tsx b/src/components/ProductHuntBanner.tsx index 2985e3e8..d090d4e3 100644 --- a/src/components/ProductHuntBanner.tsx +++ b/src/components/ProductHuntBanner.tsx @@ -9,7 +9,10 @@ interface Props { const ProductHuntBanner = (props: Props) => { const { className } = props; const { t } = useTranslation(); - const [hideBanner, setHideBanner] = useLocalStorage("hide-product-hunt-banner", false); + const [hideBanner, setHideBanner] = useLocalStorage( + "hide-product-hunt-banner", + false + ); const show = !hideBanner; return ( @@ -25,7 +28,10 @@ const ProductHuntBanner = (props: Props) => { > {t("banner.product-hunt")} -
diff --git a/src/components/QueryDrawer.tsx b/src/components/QueryDrawer.tsx index 17223b8d..177dc553 100644 --- a/src/components/QueryDrawer.tsx +++ b/src/components/QueryDrawer.tsx @@ -16,12 +16,17 @@ import ExecutionWarningBanner from "./ExecutionView/ExecutionWarningBanner"; const QueryDrawer = () => { const { t } = useTranslation(); const queryStore = useQueryStore(); - const [executionResult, setExecutionResult] = useState(undefined); + const [executionResult, setExecutionResult] = useState< + ExecutionResult | undefined + >(undefined); const [statement, setStatement] = useState(""); const [isLoading, setIsLoading] = useState(false); const context = queryStore.context; - const executionMessage = executionResult ? getMessageFromExecutionResult(executionResult) : ""; - const showExecutionWarningBanner = statement.trim() && !checkStatementIsSelect(statement); + const executionMessage = executionResult + ? getMessageFromExecutionResult(executionResult) + : ""; + const showExecutionWarningBanner = + statement.trim() && !checkStatementIsSelect(statement); useEffect(() => { if (!queryStore.showDrawer) { @@ -84,25 +89,40 @@ const QueryDrawer = () => { const close = () => queryStore.toggleDrawer(false); return ( - +
-

{t("execution.title")}

{!context ? (
- {t("execution.message.no-connection")} + + {t("execution.message.no-connection")} +
) : ( <>
{t("connection.self")}: - + {context.database?.name}
- {showExecutionWarningBanner && } + {showExecutionWarningBanner && ( + + )}
{ {isLoading ? (
- {t("execution.message.executing")} + + {t("execution.message.executing")} +
) : ( <> {executionResult ? ( executionMessage ? ( - + ) : ( - + ) ) : ( <> diff --git a/src/components/QuotaOverflowBanner.tsx b/src/components/QuotaOverflowBanner.tsx index 2ab69d41..11abd7b0 100644 --- a/src/components/QuotaOverflowBanner.tsx +++ b/src/components/QuotaOverflowBanner.tsx @@ -11,7 +11,10 @@ interface Props { const QuotaOverflowBanner = (props: Props) => { const { className } = props; const { t } = useTranslation(); - const [hideBanner, setHideBanner] = useLocalStorage("hide-quota-overflow-banner", false); + const [hideBanner, setHideBanner] = useLocalStorage( + "hide-quota-overflow-banner", + false + ); const [showSettingModal, setShowSettingModal] = useState(false); const show = !hideBanner; @@ -24,16 +27,24 @@ const QuotaOverflowBanner = (props: Props) => { >
{t("banner.quota-overflow")}{" "} -
-
- {showSettingModal && setShowSettingModal(false)} />} + {showSettingModal && ( + setShowSettingModal(false)} /> + )} ); }; diff --git a/src/components/SettingModal.tsx b/src/components/SettingModal.tsx index a20f1dae..5863bba0 100644 --- a/src/components/SettingModal.tsx +++ b/src/components/SettingModal.tsx @@ -30,7 +30,9 @@ const SettingModal = (props: Props) => {
-

{t("setting.basic.self")}

+

+ {t("setting.basic.self")} +

{t("setting.basic.language")} diff --git a/src/components/Sidebar/ConnectionList.tsx b/src/components/Sidebar/ConnectionList.tsx index ec2afb4d..a318372d 100644 --- a/src/components/Sidebar/ConnectionList.tsx +++ b/src/components/Sidebar/ConnectionList.tsx @@ -22,7 +22,8 @@ const ConnectionList = () => { showSettingModal: false, showUpdateConversationModal: false, }); - const [editConnectionModalContext, setEditConnectionModalContext] = useState(); + const [editConnectionModalContext, setEditConnectionModalContext] = + useState(); const connectionList = connectionStore.connectionList; const currentConnectionCtx = connectionStore.currentConnectionCtx; @@ -35,7 +36,9 @@ const ConnectionList = () => { }; const handleConnectionSelect = async (connection: Connection) => { - const databaseList = await connectionStore.getOrFetchDatabaseList(connection); + const databaseList = await connectionStore.getOrFetchDatabaseList( + connection + ); connectionStore.setCurrentConnectionCtx({ connection, database: head(databaseList), @@ -53,7 +56,10 @@ const ConnectionList = () => { return ( <> ))} @@ -89,7 +99,10 @@ const ConnectionList = () => { {state.showCreateConnectionModal && ( - toggleCreateConnectionModal(false)} /> + toggleCreateConnectionModal(false)} + /> )} ); diff --git a/src/components/Sidebar/ConversationList.tsx b/src/components/Sidebar/ConversationList.tsx index eb99b386..dc536496 100644 --- a/src/components/Sidebar/ConversationList.tsx +++ b/src/components/Sidebar/ConversationList.tsx @@ -1,6 +1,10 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { useConversationStore, useConnectionStore, useLayoutStore } from "@/store"; +import { + useConversationStore, + useConnectionStore, + useLayoutStore, +} from "@/store"; import { Conversation } from "@/types"; import Dropdown, { DropdownItem } from "../kit/Dropdown"; import Icon from "../Icon"; @@ -18,7 +22,8 @@ const ConversationList = () => { const [state, setState] = useState({ showUpdateConversationModal: false, }); - const [updateConversationModalContext, setUpdateConversationModalContext] = useState(); + const [updateConversationModalContext, setUpdateConversationModalContext] = + useState(); const currentConnectionCtx = connectionStore.currentConnectionCtx; const conversationList = conversationStore.conversationList.filter( (conversation) => @@ -37,7 +42,10 @@ const ConversationList = () => { if (!currentConnectionCtx) { conversationStore.createConversation(); } else { - conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name); + conversationStore.createConversation( + currentConnectionCtx.connection.id, + currentConnectionCtx.database?.name + ); } }; @@ -69,7 +77,8 @@ const ConversationList = () => {
handleConversationSelect(conversation)} > @@ -78,7 +87,9 @@ const ConversationList = () => { ) : ( )} - {conversation.title || "SQL Chat"} + + {conversation.title || "SQL Chat"} + @@ -114,7 +125,10 @@ const ConversationList = () => { {updateConversationModalContext && state.showUpdateConversationModal && ( - toggleUpdateConversationModal(false)} conversation={updateConversationModalContext} /> + toggleUpdateConversationModal(false)} + conversation={updateConversationModalContext} + /> )} ); diff --git a/src/components/ThemeSelector.tsx b/src/components/ThemeSelector.tsx index 86e0782d..36bcb757 100644 --- a/src/components/ThemeSelector.tsx +++ b/src/components/ThemeSelector.tsx @@ -32,7 +32,14 @@ const ThemeSelector = () => { settingStore.setTheme(theme); }; - return + ); }; export default ThemeSelector; diff --git a/src/components/ThemeSwitch.tsx b/src/components/ThemeSwitch.tsx index e7555081..9098ea25 100644 --- a/src/components/ThemeSwitch.tsx +++ b/src/components/ThemeSwitch.tsx @@ -14,7 +14,10 @@ const ThemeSwitch = () => { }; return ( - ); diff --git a/src/components/UpdateConversationModal.tsx b/src/components/UpdateConversationModal.tsx index d6feedb7..c883f39c 100644 --- a/src/components/UpdateConversationModal.tsx +++ b/src/components/UpdateConversationModal.tsx @@ -27,7 +27,9 @@ const UpdateConversationModal = (props: Props) => { label: assistant.name, }; }); - const currentAssistant = assistantList.find((assistant) => assistant.id === assistantId); + const currentAssistant = assistantList.find( + (assistant) => assistant.id === assistantId + ); const handleSaveEdit = () => { const formatedTitle = title.trim(); @@ -46,17 +48,30 @@ const UpdateConversationModal = (props: Props) => { return (
- - setTitle(value)} /> + + setTitle(value)} + />
- setAssistantId(value)} + /> {currentAssistant && (
-

{currentAssistant.description}

+

+ {currentAssistant.description} +

)} { href="https://github.com/sqlchat/sqlchat/tree/main/assistants" target="_blank" > - {t("assistant.create-your-bot")} + {t("assistant.create-your-bot")}{" "} +
diff --git a/src/components/kit/Modal.tsx b/src/components/kit/Modal.tsx index b328a54f..5fccfd34 100644 --- a/src/components/kit/Modal.tsx +++ b/src/components/kit/Modal.tsx @@ -19,7 +19,9 @@ const Modal = (props: Props) => { className || "" } flex flex-col bg-white dark:bg-zinc-800 rounded-xl p-4 fixed top-[50%] left-[50%] h-auto max-h-[85vh] w-[90vw] max-w-[90vw] sm:max-w-lg translate-x-[-50%] translate-y-[-50%] z-100 outline-none`} > -

{title}

+

+ {title} +

-
{children}
+
+ {children} +
); diff --git a/src/components/kit/Popover.tsx b/src/components/kit/Popover.tsx index 0873cf87..f67d5f08 100644 --- a/src/components/kit/Popover.tsx +++ b/src/components/kit/Popover.tsx @@ -16,7 +16,9 @@ const Popover = (props: Props) => { {children} diff --git a/src/components/kit/Select.tsx b/src/components/kit/Select.tsx index b8f03d0c..e5276b92 100644 --- a/src/components/kit/Select.tsx +++ b/src/components/kit/Select.tsx @@ -1,5 +1,5 @@ import * as SelectUI from "@radix-ui/react-select"; -import * as ScrollArea from '@radix-ui/react-scroll-area'; +import * as ScrollArea from "@radix-ui/react-scroll-area"; import React from "react"; import Icon from "../Icon"; @@ -37,18 +37,34 @@ const Select = (props: Props) => { }} position="popper" > - 7 ? "h-80 overflow-hidden" : "max-h-80 overflow-auto"} border dark:border-zinc-800 rounded-lg drop-shadow-lg`} type="auto"> - + 7 + ? "h-80 overflow-hidden" + : "max-h-80 overflow-auto" + } border dark:border-zinc-800 rounded-lg drop-shadow-lg`} + type="auto" + > + - - {placeholder && {placeholder}} + + {placeholder && ( + + {placeholder} + + )} {itemList.map((item) => ( - {item.label} + + {item.label} + diff --git a/src/components/kit/TextField.tsx b/src/components/kit/TextField.tsx index da261d96..604cdfe5 100644 --- a/src/components/kit/TextField.tsx +++ b/src/components/kit/TextField.tsx @@ -19,11 +19,16 @@ const getDefaultProps = () => ({ }); const TextField = (props: Props) => { - const { value, disabled, className, placeholder, type, onChange } = { ...getDefaultProps(), ...props }; + const { value, disabled, className, placeholder, type, onChange } = { + ...getDefaultProps(), + ...props, + }; return ( Promise; - execute: (databaseName: string, statement: string) => Promise; + execute: ( + databaseName: string, + statement: string + ) => Promise; getDatabases: () => Promise; getTables: (databaseName: string) => Promise; - getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => Promise; + getTableStructure: ( + databaseName: string, + tableName: string, + structureFetched: (tableName: string, structure: string) => void + ) => Promise; } export const newConnector = (connection: Connection): Connector => { diff --git a/src/lib/connectors/mssql/index.ts b/src/lib/connectors/mssql/index.ts index 4adb642e..b7f0138f 100644 --- a/src/lib/connectors/mssql/index.ts +++ b/src/lib/connectors/mssql/index.ts @@ -4,7 +4,9 @@ import { Connector } from ".."; const systemDatabases = ["master", "tempdb", "model", "msdb"]; -const getMSSQLConnection = async (connection: Connection): Promise => { +const getMSSQLConnection = async ( + connection: Connection +): Promise => { const connectionOptions: any = { server: connection.host, port: parseInt(connection.port), @@ -32,7 +34,11 @@ const testConnection = async (connection: Connection): Promise => { return true; }; -const execute = async (connection: Connection, databaseName: string, statement: string): Promise => { +const execute = async ( + connection: Connection, + databaseName: string, + statement: string +): Promise => { const pool = await getMSSQLConnection(connection); const request = pool.request(); const result = await request.query(`USE ${databaseName}; ${statement}`); @@ -48,7 +54,11 @@ const execute = async (connection: Connection, databaseName: string, statement: const getDatabases = async (connection: Connection): Promise => { const pool = await getMSSQLConnection(connection); const request = pool.request(); - const result = await request.query(`SELECT name FROM sys.databases WHERE name NOT IN ('${systemDatabases.join("','")}');`); + const result = await request.query( + `SELECT name FROM sys.databases WHERE name NOT IN ('${systemDatabases.join( + "','" + )}');` + ); await pool.close(); const databaseList = []; for (const row of result.recordset) { @@ -59,7 +69,10 @@ const getDatabases = async (connection: Connection): Promise => { return databaseList; }; -const getTables = async (connection: Connection, databaseName: string): Promise => { +const getTables = async ( + connection: Connection, + databaseName: string +): Promise => { const pool = await getMSSQLConnection(connection); const request = pool.request(); const result = await request.query( @@ -75,7 +88,12 @@ const getTables = async (connection: Connection, databaseName: string): Promise< return tableList; }; -const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise => { +const getTableStructure = async ( + connection: Connection, + databaseName: string, + tableName: string, + structureFetched: (tableName: string, structure: string) => void +): Promise => { const pool = await getMSSQLConnection(connection); const request = pool.request(); const { recordset } = await request.query( @@ -86,21 +104,32 @@ const getTableStructure = async (connection: Connection, databaseName: string, t // Transform to standard schema string. for (const row of recordset) { columnList.push( - `${row["COLUMN_NAME"]} ${row["DATA_TYPE"].toUpperCase()} ${String(row["IS_NULLABLE"]).toUpperCase() === "NO" ? "NOT NULL" : ""}` + `${row["COLUMN_NAME"]} ${row["DATA_TYPE"].toUpperCase()} ${ + String(row["IS_NULLABLE"]).toUpperCase() === "NO" ? "NOT NULL" : "" + }` ); } - structureFetched(tableName, `CREATE TABLE [${tableName}] ( + structureFetched( + tableName, + `CREATE TABLE [${tableName}] ( ${columnList.join(",\n")} - );`); + );` + ); }; const newConnector = (connection: Connection): Connector => { return { testConnection: () => testConnection(connection), - execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement), + execute: (databaseName: string, statement: string) => + execute(connection, databaseName, statement), getDatabases: () => getDatabases(connection), getTables: (databaseName: string) => getTables(connection, databaseName), - getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched), + getTableStructure: ( + databaseName: string, + tableName: string, + structureFetched: (tableName: string, structure: string) => void + ) => + getTableStructure(connection, databaseName, tableName, structureFetched), }; }; diff --git a/src/lib/connectors/mysql/index.ts b/src/lib/connectors/mysql/index.ts index 788799ce..2abb90b6 100644 --- a/src/lib/connectors/mysql/index.ts +++ b/src/lib/connectors/mysql/index.ts @@ -3,9 +3,16 @@ import mysql, { RowDataPacket } from "mysql2/promise"; import { Connection, ExecutionResult } from "@/types"; import { Connector } from ".."; -const systemDatabases = ["information_schema", "mysql", "performance_schema", "sys"]; +const systemDatabases = [ + "information_schema", + "mysql", + "performance_schema", + "sys", +]; -const getMySQLConnection = async (connection: Connection): Promise => { +const getMySQLConnection = async ( + connection: Connection +): Promise => { const connectionOptions: ConnectionOptions = { host: connection.host, port: parseInt(connection.port), @@ -30,7 +37,11 @@ const testConnection = async (connection: Connection): Promise => { return true; }; -const execute = async (connection: Connection, databaseName: string, statement: string): Promise => { +const execute = async ( + connection: Connection, + databaseName: string, + statement: string +): Promise => { connection.database = databaseName; const conn = await getMySQLConnection(connection); const [rows] = await conn.execute(statement); @@ -64,7 +75,10 @@ const getDatabases = async (connection: Connection): Promise => { return databaseList; }; -const getTables = async (connection: Connection, databaseName: string): Promise => { +const getTables = async ( + connection: Connection, + databaseName: string +): Promise => { const conn = await getMySQLConnection(connection); const [rows] = await conn.query( `SELECT TABLE_NAME as table_name FROM information_schema.tables WHERE TABLE_SCHEMA=? AND TABLE_TYPE='BASE TABLE';`, @@ -80,9 +94,16 @@ const getTables = async (connection: Connection, databaseName: string): Promise< return tableList; }; -const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise => { +const getTableStructure = async ( + connection: Connection, + databaseName: string, + tableName: string, + structureFetched: (tableName: string, structure: string) => void +): Promise => { const conn = await getMySQLConnection(connection); - const [rows] = await conn.query(`SHOW CREATE TABLE \`${databaseName}\`.\`${tableName}\`;`); + const [rows] = await conn.query( + `SHOW CREATE TABLE \`${databaseName}\`.\`${tableName}\`;` + ); conn.destroy(); if (rows.length !== 1) { throw new Error("Unexpected number of rows."); @@ -93,10 +114,16 @@ const getTableStructure = async (connection: Connection, databaseName: string, t const newConnector = (connection: Connection): Connector => { return { testConnection: () => testConnection(connection), - execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement), + execute: (databaseName: string, statement: string) => + execute(connection, databaseName, statement), getDatabases: () => getDatabases(connection), getTables: (databaseName: string) => getTables(connection, databaseName), - getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched), + getTableStructure: ( + databaseName: string, + tableName: string, + structureFetched: (tableName: string, structure: string) => void + ) => + getTableStructure(connection, databaseName, tableName, structureFetched), }; }; diff --git a/src/lib/connectors/postgres/index.ts b/src/lib/connectors/postgres/index.ts index fa4fd96a..8eec6501 100644 --- a/src/lib/connectors/postgres/index.ts +++ b/src/lib/connectors/postgres/index.ts @@ -27,7 +27,11 @@ const testConnection = async (connection: Connection): Promise => { return true; }; -const execute = async (connection: Connection, databaseName: string, statement: string): Promise => { +const execute = async ( + connection: Connection, + databaseName: string, + statement: string +): Promise => { connection.database = databaseName; const client = newPostgresClient(connection); await client.connect(); @@ -65,7 +69,10 @@ const getDatabases = async (connection: Connection): Promise => { return databaseList; }; -const getTables = async (connection: Connection, databaseName: string): Promise => { +const getTables = async ( + connection: Connection, + databaseName: string +): Promise => { connection.database = databaseName; const client = newPostgresClient(connection); await client.connect(); @@ -83,7 +90,12 @@ const getTables = async (connection: Connection, databaseName: string): Promise< return tableList; }; -const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise => { +const getTableStructure = async ( + connection: Connection, + databaseName: string, + tableName: string, + structureFetched: (tableName: string, structure: string) => void +): Promise => { connection.database = databaseName; const client = newPostgresClient(connection); await client.connect(); @@ -96,21 +108,32 @@ const getTableStructure = async (connection: Connection, databaseName: string, t // TODO(steven): transform it to standard schema string. for (const row of rows) { columnList.push( - `${row["column_name"]} ${row["data_type"].toUpperCase()} ${String(row["is_nullable"]).toUpperCase() === "NO" ? "NOT NULL" : ""}` + `${row["column_name"]} ${row["data_type"].toUpperCase()} ${ + String(row["is_nullable"]).toUpperCase() === "NO" ? "NOT NULL" : "" + }` ); } - structureFetched(tableName, `CREATE TABLE \`${tableName}\` ( + structureFetched( + tableName, + `CREATE TABLE \`${tableName}\` ( ${columnList.join(",\n")} - );`); + );` + ); }; const newConnector = (connection: Connection): Connector => { return { testConnection: () => testConnection(connection), - execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement), + execute: (databaseName: string, statement: string) => + execute(connection, databaseName, statement), getDatabases: () => getDatabases(connection), getTables: (databaseName: string) => getTables(connection, databaseName), - getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched), + getTableStructure: ( + databaseName: string, + tableName: string, + structureFetched: (tableName: string, structure: string) => void + ) => + getTableStructure(connection, databaseName, tableName, structureFetched), }; }; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 7f53cb4e..3d787651 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -52,7 +52,10 @@ function MyApp({ Component, pageProps }: AppProps) { const theme = settingStore.setting.theme; let currentAppearance = theme; if (theme === "system") { - if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { currentAppearance = "dark"; } else { currentAppearance = "light"; diff --git a/src/pages/api/chat.ts b/src/pages/api/chat.ts index a57ecc20..4eb892e1 100644 --- a/src/pages/api/chat.ts +++ b/src/pages/api/chat.ts @@ -1,4 +1,8 @@ -import { createParser, ParsedEvent, ReconnectInterval } from "eventsource-parser"; +import { + createParser, + ParsedEvent, + ReconnectInterval, +} from "eventsource-parser"; import { NextRequest } from "next/server"; import { API_KEY } from "@/env"; import { openAIApiEndpoint, openAIApiKey } from "@/utils"; @@ -26,7 +30,9 @@ const handler = async (req: NextRequest) => { const reqBody = await req.json(); const openAIApiConfig = reqBody.openAIApiConfig; const apiKey = openAIApiConfig?.key || openAIApiKey; - const apiEndpoint = getApiEndpoint(openAIApiConfig?.endpoint || openAIApiEndpoint); + const apiEndpoint = getApiEndpoint( + openAIApiConfig?.endpoint || openAIApiEndpoint + ); const res = await fetch(apiEndpoint, { headers: { "Content-Type": "application/json", diff --git a/src/pages/api/connection/db_schema.ts b/src/pages/api/connection/db_schema.ts index b50c79ca..cc8280a2 100644 --- a/src/pages/api/connection/db_schema.ts +++ b/src/pages/api/connection/db_schema.ts @@ -16,18 +16,20 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { const connector = newConnector(connection); const tableStructures: Table[] = []; const rawTableNameList = await connector.getTables(db); - const structureFetched = (tableName:string, structure:string) => { + const structureFetched = (tableName: string, structure: string) => { tableStructures.push({ name: tableName, structure, }); - } - Promise.all(rawTableNameList.map(async (tableName) => - connector.getTableStructure(db, tableName, structureFetched) - )).then(() => { + }; + Promise.all( + rawTableNameList.map(async (tableName) => + connector.getTableStructure(db, tableName, structureFetched) + ) + ).then(() => { res.status(200).json({ data: tableStructures, - }); + }); }); } catch (error: any) { res.status(400).json({ diff --git a/src/pages/api/usage.ts b/src/pages/api/usage.ts index 7f8cdff5..73d7c43b 100644 --- a/src/pages/api/usage.ts +++ b/src/pages/api/usage.ts @@ -4,7 +4,10 @@ import { Conversation, Message } from "@/types"; const prisma = new PrismaClient(); -export default async function handler(req: NextApiRequest, res: NextApiResponse) { +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { if (req.method !== "POST") { return res.status(405).json([]); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 9b5f777d..4cd999cd 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -6,12 +6,18 @@ import React from "react"; // Use dynamic import to avoid page hydrated. // reference: https://github.com/pmndrs/zustand/issues/1145#issuecomment-1316431268 -const ConnectionSidebar = dynamic(() => import("@/components/ConnectionSidebar"), { - ssr: false, -}); -const ConversationView = dynamic(() => import("@/components/ConversationView"), { - ssr: false, -}); +const ConnectionSidebar = dynamic( + () => import("@/components/ConnectionSidebar"), + { + ssr: false, + } +); +const ConversationView = dynamic( + () => import("@/components/ConversationView"), + { + ssr: false, + } +); const QueryDrawer = dynamic(() => import("@/components/QueryDrawer"), { ssr: false, }); @@ -20,14 +26,31 @@ const IndexPage: NextPage = () => { return (
- SQL Chat - Chat-based SQL Client and Editor for the next decade - + + SQL Chat - Chat-based SQL Client and Editor for the next decade + + - - + + - +

SQL Chat

@@ -38,7 +61,11 @@ const IndexPage: NextPage = () => { -