From 8d2441868d564504dabb9031024f8fdd5eea0894 Mon Sep 17 00:00:00 2001 From: John Gutierrez <75353216+canilo1@users.noreply.github.com> Date: Sun, 20 Apr 2025 00:00:06 -0700 Subject: [PATCH 1/5] React.js implementation, still working on making all the files compatible --- .../test_app.cpython-38-pytest-8.3.5.pyc | Bin 0 -> 3959 bytes tests/test_app.py | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 tests/__pycache__/test_app.cpython-38-pytest-8.3.5.pyc diff --git a/tests/__pycache__/test_app.cpython-38-pytest-8.3.5.pyc b/tests/__pycache__/test_app.cpython-38-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54bfb9a7d7ca04e3fc055364544c360dbb692a1a GIT binary patch literal 3959 zcmeHKPjB2r6rZuZw%7Y7Y11Yxe-~&=7TQhIG!38zTD1j*Ll88DvWl==&m{5IYp-UU z(ggcLd*DVtKo8ktz6S?Rd;lM~AR&6=oD=Vjcas$bDME_Gp61HcHQA!$?f)VjJxDCrvQ5-obnwyAoow#Chky;Ej{ zTkv!gRuoowtxuWO*ZQ=t4Zf?l>+qc(j&;Y|YnKR^PqMduZk_)-&dm@ZiKa2QH5j^0lH@Uo@#J$xUF$<#g z)fL|DeH@333y_2%U*{{ri{u&?*O&f5fNy!e(LZ4vCrGh;ei}GNBhl7}Y1z8c(AF6jWCXA(e zs8}qB#fDgL^=r#}bmrO5FlW1TPg`8vrQ367r+ZtMn$r1h&}b|+uywI1TQc#IP3ihE z<4)FCEIQ_!GPebny*QG5SI>3OMYx18zgPDpkR(wy7C;q*3vvQisvtvS$cD!CB_Np= zL`}MV9zD)BWh02xhv1`#2O5KT<{%#CC7zQtmCsqUJSWD%apaOTBk|_Wwcb|2 zDv=*^eJ>1M!F$4wyFE|vyb9g!z0Dv@f=K3O7=wn)Pq}g<-VB*ib=SlAo)@~2*X4QH zl^#$SEadg;YJ@=?-4Y@e`GmU`i>{Zr{9!NjA}@iS+`w3yWkp<3g&^|^dMqYqcJ$@) z=*wL3QW(54`tn|T#K6VTZ!W3)EC5p`A5-Jrh`Zbs@n*tnAe3(c5J)8x_-VtvOsb?# zs?;DRyqkk}@iu7vd!l{#KTWih?l^stSqY?5QhlUS9un;eD>G+5#ec$dN-B`zzhF9H z3sM|*;eCzYRl-3Mia%AtK?^i0tUA;Hn^k3{+NV#bm{fM44O%F-QUlVm_SArF!5(Z2 z_HbLs43&cQrl2n(6xdDxXT%8_d z(0P?Vkk3sg%bh@?Col53Xn-AHW#A{fhNz?E69{7fZOEj{yEQS1f+>Vk2&WNVLUFWx{YGEcmPa0cNlLIa>6bV2p`zjOGc;xL*Y zohzISiSGZ2$u@O>$0@K{v_Rtpi;tGY)=U&fzJD103M^3yiZrl=JQ43AoJY8TfHlT* zdl;^N;RP9an3bOay%#Hgv@8G3OxNqgLVhH9geBk7!EUz17O%{n6Wr&)gXek{-aqI0 z9^>7>|C)OObQk8&`Ch`;W3dI*BVSZ@53wQs2SQEkZzxy_6a&DWT&(69-Xlu|TN$({|mpAmpx_n}x6T zlwTCoZZi0L<$6I2{z4W1>8!QjVX`TBQGjAqrw&Dd`=dZU3mnSGuB-N6RX*BRyD{4g cITm|FVgX>2 Date: Sun, 20 Apr 2025 00:06:18 -0700 Subject: [PATCH 2/5] [change] removed dependency and changed endpoint --- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 186 bytes requirements.txt | 1 - .../test_app.cpython-313-pytest-8.3.5.pyc | Bin 7536 -> 9615 bytes .../test_e2e.cpython-313-pytest-8.3.5.pyc | Bin 0 -> 2424 bytes tests/test_app.py | 8 +++++--- 5 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 college_transfer_ai/__pycache__/__init__.cpython-313.pyc create mode 100644 tests/__pycache__/test_e2e.cpython-313-pytest-8.3.5.pyc diff --git a/college_transfer_ai/__pycache__/__init__.cpython-313.pyc b/college_transfer_ai/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3e011c55415b55ac383e76ec2eafdcc05caaeb7 GIT binary patch literal 186 zcmey&%ge<81UmgJ86f&Gh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABy}s&*(xTqIJKxa zCNICFBtOO_wK%&ZzW`1L6y;~7CYKb)IOpf&q^75a6eZ>rr==D-dd4I}MB+=JLh*^2 sG4b)4d6^~g@p=W7w>WHa^HWN5QtgUZfi{6$QVe2zWM*V!EMf+-0HH`SNdN!< literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt index b8269d7..1c04822 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ Flask flask-cors requests pymongo -gridfs playwright jinja2 pytest \ No newline at end of file diff --git a/tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc b/tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc index 274f595e84ef9ce65eef6d1b804dcf9f5dbff1cc..e79f0bf8510d634c22bdc8cfb3c75f3a63076128 100644 GIT binary patch delta 794 zcmexh)$gtGnU|M~0SL^ev1CZGGB7*_abN%}1LS-TVVtO;Sz?o;5=-ixh(?RJ=$im{p3QNH~}cNQwlr14+?f4j?HO z%n2mL(-}3nUV_y6RWa%5ZCv2V$jGtz72|tWM!wCr*l)8j3UA)VcZQWmT)!waIW@B^ zGcR3tatW{gVlNNON2Vqg0o9tDswF3uq^9Q= zRXXRVq}qbbo!r2y40MILp>RoRaY=l7YDs)zPEI_;&LSJ2)8w%@uj(>?HPCsMY^-)b zS=G&c(k6_I5|f){Tr8N2D~oS2E;v7r`u1oUtqM{e4+Vrn}s$Tn6|TTWZ%xek^cb4WdqL> iCYO~1E(-@zYEUZ8P delta 272 zcmeD8{$Qo?nU|M~0SMNfVanLY%)sy%#DM`ODC2V;!$b{DZYFPbFODJ(1%?>5$q!jX z41$@Z7)+r`inxMVq!^00gIR$jPcR#himnj9}ds{A&0F?upGvTVM>^q!TG zYx5C~+iZ;dn-}n(VP$96&&$tCPM%z#6g~N#sHnIoP<^o~BLhPN!wr7X{;JNZ8Jd^* zRW>QhFm66BE6d7i15_Zr`K6KxBP0K0ITe@5vsK*nS27go0>z6sfy6Bio80`A(wtPg mB3B?6XcR-SC6M^Q%*e=ipMjx;p?Pw+swUTG9u`Iwur>g#)jT`^ diff --git a/tests/__pycache__/test_e2e.cpython-313-pytest-8.3.5.pyc b/tests/__pycache__/test_e2e.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..754a686629f74ea7a3a2be791ee2d3051a3d2b1f GIT binary patch literal 2424 zcmcgu&2Jk;6rZ&nd+o&8#-UM2)n*$Ps8i~gwk~O*K$RByA(bjyOQ1wrP1X~8+x5(IREJN5qhiDrh+h zCQ`$^JTk!aNK3O~=QLldXv?YRwUJFfR*5aApVwRQa?ihs05Q4`A~A%>?t>T^LhNVb z#fapPjsMC^>VNT~|Lb1%%SU?1i?PqS^bvmC%Fs)d_&WfimE%2V^?QU?4n*sga}l*S z&mn8HGIB<<#y(j(6Dvc#s%K!$D_zOpIEO0zim|dQQ;}|YI->UGITY2WimZBQ$o9-0 zE~3D8zh0wVVDRw5i2AEO)~u{GUW}bgBIGA6!#Yq&mb8#dtsIX04A?so$@XT5qF+bR z84zd^7qs9q+`^-K$cTz|88!}R9_8NMHc<^7MQ@^LFIBjU>(FFokqMs~x)7g5>sn#r z#w}uay)rAPoFD!V}!CswmalTwCl@?mLJSUhr^{YM?u4oFE`mOO|mC>L| zH~f>XAdS|+JR=TqKXd)s5G;=!TqORx01XwKG`q@UMoU`G1o6>t=wzJgUz=JT&Wd#y1?aV*N831T~Ub1H~`dex;2hD zCrpIEG5|qXE=(}hAEoZp8Ev@DhD>_6={t3)gEKq8H6jx=DySTBJlDA+CpNGv>~+d) zPCViW%4Ej%eZoS2GV?*ODF_!fW^%%YlzBEI0dr_0z>LVW&G9->5;+D)d#&ku!u2_L zRr%#|+U9lI^s3=1yXMg~?AbnU5IKG^j2`lQnKAG>se))=VUxSi{C%uWKm|!TDd5a>yuNgPwE{-@s!xTWtCtAB zBWTc{Z!&rtvcgxFsOOOyxy7*0VaFG*uBx=~aJ0!OQVcew8D^>m=gY7lHm)&38gTIT zItweH%JSe_oe$rHYW*Djnx5{YXTHNbxyf(mAI@**@>{w5<6G@qzMVUDFSTP#Z5y*& z#_SX0=)J^Kee6N{e!88T@8~Cg{`@J4T_cFFsz`G8VV>-uEYc zv27H(Y-fP(13>tArO#~*aeeU0PS5<3p81=P!4Z7<(a9@X z{6{Tw#Yh#>QjbcP9R#I()$Z1Pv0GeFi$zA@g+xHj_eC<*lQGr*<>cN=OEG+j!GRmz z#_-sGK)V1!Y)(P-cN?_Y^vHYc1h7yGh$|D%;+m%YhR_%2XSA@JFtuqm58{6SXe{=| literal 0 HcmV?d00001 diff --git a/tests/test_app.py b/tests/test_app.py index b6ccf8f..3c376ad 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,3 +1,7 @@ +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + import pytest from college_transfer_ai.app import app @@ -19,7 +23,7 @@ def test_get_institutions(client): assert isinstance(data, (dict)) def test_get_nonccs(client): - response = client.get('/nonccs') + response = client.get('/receiving-institutions') assert response.status_code == 200 data = response.get_json() assert isinstance(data, (dict)) @@ -35,5 +39,3 @@ def test_get_academic_years(client): assert response.status_code == 200 data = response.get_json() assert isinstance(data, (dict)) - -# Add more tests for other endpoints as needed \ No newline at end of file From 52bbd478db65e15bc84c8f152cafabe356a5b2a1 Mon Sep 17 00:00:00 2001 From: John Gutierrez <75353216+canilo1@users.noreply.github.com> Date: Sun, 20 Apr 2025 00:22:13 -0700 Subject: [PATCH 3/5] Implemented to React --- .gitignore | 24 + .../__pycache__/app.cpython-38.pyc | Bin 0 -> 3290 bytes .../college_transfer_API.cpython-38.pyc | Bin 0 -> 5281 bytes eslint.config.js | 33 + index.html | 7 + package-lock.json | 2410 +++++++++++++++++ package.json | 27 + public/vite.svg | 1 + src/App.css | 50 + src/App.jsx | 12 + src/assets/react.svg | 1 + src/components/CollegeTransferAI.jsx | 73 + src/index.css | 68 + {static => src}/js/main.js | 36 +- src/main.jsx | 10 + templates/index.html | 54 - vite.config.js | 7 + 17 files changed, 2741 insertions(+), 72 deletions(-) create mode 100644 .gitignore create mode 100644 college_transfer_ai/__pycache__/app.cpython-38.pyc create mode 100644 college_transfer_ai/__pycache__/college_transfer_API.cpython-38.pyc create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/vite.svg create mode 100644 src/App.css create mode 100644 src/App.jsx create mode 100644 src/assets/react.svg create mode 100644 src/components/CollegeTransferAI.jsx create mode 100644 src/index.css rename {static => src}/js/main.js (93%) create mode 100644 src/main.jsx delete mode 100644 templates/index.html create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/college_transfer_ai/__pycache__/app.cpython-38.pyc b/college_transfer_ai/__pycache__/app.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c0728e1c1735f40afca47740e100880ebcbd01e GIT binary patch literal 3290 zcmc&$OK%%T67KGKa7dA&EZch7mSra!gu93s?9s5ZwrM*MkSKxWz{xgvZ-)z|g-rcx;pcp9;N zEGvZk8wc}G0R|7DslQV~IN>xVK7LtD6XvtT@~y=7?Zok&#P!{z;1?*)Td|iE{i31m zxRfmUONOR#*)N-ODt-mNPQ08{{VFAboU9D5n10ptSH-mvKjH;k|FD~5l= z@K)|qP@U$56P2Y=tCts~_`WNY&Z|mMT%Bx!w5c`2l=a8(AESws;}KRJDYB^z|Na zm%hx>cGid^k?KC%-OY>BlU4329qOoAV}5nY3|d(XzxONSsmbs+T5qD{^ACeT1Df)n z)1cpL)R67>OA#pW!|t(8;u@6>%>GK`GJH(;Tc>gF@EawhI&6kA&JMpy#SvQ6`O_C*u0kU8amLjJb#WWIkx}+5oE9^vmxh-@sQB6q;ew`Pe{?HU16gsz*mH=LPUXh%NcgB`g+_8d(%&%Dot}=0f zG3wD0U7>Ol)hg7}ENwRb=h{m|r{>7(DDD!93vvV?h*4Rt0b8Qbplo3}vw_UnwKAH@ zE#U1yTT~AD{c1gIhFm03^OK$k<^Na7Ra9~+PhjMEU)SY@1(&IO15~SBqB8OU&m=s_ z7F2a%W_FwZ2?I?|=n!t}h{Amy(-4%;>4dg`W)s>5+UjpUVsd>RmSH@n7^ZoUDC?RZj zAw578_#|vgArc7h7h)DB_8H1U7+iufAU?GC7;A`B+}ea#23R9)8`?6oV`#g-t(~De zuo3kE0o(yu?hYJ`Z<7e<0rtND_O8Lczze!Ka3|P%3)s7_+zZ$j_jBu1^sr1JZy^Sf zw;1mvQhB+Ac|l@_~a zvFaU#e0OrxZYp&BZxS4+_fke$s0Uhh#Yg{mU~YA!dmlm8-3OE&yqC2b=0NFh{LxJA zO>I+S{mR!*p6#TW-f3lB2nauevHDdwJC4Ae;NGJ#Yt;9W2vT;hBWkY1`<}aP8S$3# z-Ou6s?7)XJp?(6rCAvgzOA&HNH5fBb7flbCxOJX<@A=v5FX9ImFo%MiOJOk;^Zc zMGzsRl2}psA5Q?8uV{AP<3W)pi?BSmv67U3#+`*u&ww?yhusPFab5_Ilp*YsEK2hN zl#=L5C^hOF1itt9(ZSQ;$=-A1;am`44bm}z(syLm)xuOxrc^MgR$`Ko7?aEeAKSr_ z-E_``CPSMt#Jitlyc>(p@Y<{U(0O#n1UNKinQc=BFF4MBUB{y?n7Cvw(~>vWsz6@> Iez8P<2RThCtpET3 literal 0 HcmV?d00001 diff --git a/college_transfer_ai/__pycache__/college_transfer_API.cpython-38.pyc b/college_transfer_ai/__pycache__/college_transfer_API.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab8028d76d569b1951b99d945171bab7af0692f4 GIT binary patch literal 5281 zcmb_gTW{RP6`tX3xmU~b-3i=HoCw_{mfSQ+8`W}b%Qv<4#*G|1ArvqaXI7%bC09eP z|*=ZQxxpa0p^nJd;9|Vwal zZ*@}Bw!OfMZ5sy^;r9{ReP!}3pgYhzGAAae>u|_9c?OMyY{9&oBRv|^10|u)X;1M; zPlZEElr5Dh+lqKEQ4X}{q^Bo(PYujiXX-xv6HV0HA9bCM?^3F0YmFvy6bp8XVo~Y zHhAoAR-L|1wS!8jdDroM)PI3cAJQw`{JU4*eg6uG4^m}?)p7#AH8lm_Y4a$KQgwsJ zX)%hOxEu#REB2tNMzN@w!oUYrpa4D{wZb6c)){EE{n#E1!5B zS*82tG4Q`^%avq7Tky-nfiIOr_zD^ffRD)mO%$MjgugqJs0T_PcI^oXUe!pfZWqLL zO7I_ROxdTDd`<%8GYBo{*ok%M?&1_Q1|HoIVbk{5{53yx9Y4B0KQfl;fz#xXKt87$whw7Fr8W@q z1ayfR6tj4<@YwO&{EiTzNcEQRf;iQ2q>&tZniCuXw##dI$z*{P5@+y1BO`HI?2|d; zKT4Z9v16SapWuwkbUUTHlT&aC2{!RbRd_*k9t5GtQ;jOXRg+d|MX4w=v_!je|J~e7 zr|Mt9`2R{(BV zZEuc;Hv#FJhzbgHa8X840s#^0D9+*yy!9rEITVtZlbDL1;;kBrw@@G@Q`KWnQ+*d% zG6~%bLWl9l?o;t+@WAo7sV1F)fA{hf+{Ue>dehkog^Nx%_~DmLgqcM74r-Ewf>aX~ zq?Tykk?+l(F2j!w;m3gNlY_&A5M;;@luz=#+*k;*h9O8Fg`gzY4?3>{o`qg9DFV)? zd!?j=!3Iz*VZec`)7oK)3GQyz4?moOm$M-_Z$fz!eI&TcyEDj{HuygR_Z7K%s?PGt#n_TLL|uKeJd`e-oX#OVaT7q!v)lEFY`y2{4ped4B*dz zJGmiu%rWjfK9)OXpF8)*xU-1d>G!__{pDUIDKi6N!_%aaX!0yMg>hhTKoWbpO`=vt)FfgyQ1DC8&MJ%yvJ-?9 zW{#v@L~Mv!3oamHQ;E)z`Vzcb85S7o7(%y(2#pmOvf6wdqc&47U}R{T7t^mGyCx+@I!4Q>M9ZF0p&N73VG8*P(9xe3qXg<#ElW(r~3Q1wh+{&{eD}7Bd8y*y( zn1_hARZQ}%%qm~Qo&a2x*abTjivj=~8ItGGH*Yg^Xy?M_BAEb<1;O&b-tu&J9b2rEWO7c+_d}r8u11 z_4wY+VKMQsTx9k?0&gu1pO%`pI@yQGi`A^W7(Knzfb|0JEB=OemLAn$n`Dza%;!;L z6=AOSLb2_^CEO_NIj~RA4r0%@l$-Ct;>Ku%qUpc}j`%fzR;!3Ba1vKhs31~fLwKwa zrN%ut-(5{%egb9#hNSIcFWn|mbMYbp``*D zQnl5N;T4$8v#d2tVpe{bHsnB3!<9QpslwJ%r4eNd71~O3>muBP*IJr1o(Hq`dIv}_ z89pzfV>=@D+_vxF($F4G>)+u05!TK`nfp;Oa6M8`D#Y o)sbE%!56 + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..862eadd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2410 @@ +{ + "name": "collegetransferai", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "collegetransferai", + "version": "0.0.0", + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react-swc": "^3.8.0", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.0.tgz", + "integrity": "sha512-iWhsUS8Wgxz9AXNfvfOPFSW4VfMXdVhp1hjkZVhXCrpgh/aLcc45rX6MPu+tIVUWDw0HfNwth7O28M1xDxNf9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.21.tgz", + "integrity": "sha512-/Y3BJLcwd40pExmdar8MH2UGGvCBrqNN7hauOMckrEX2Ivcbv3IMhrbGX4od1dnF880Ed8y/E9aStZCIQi0EGw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.21" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.11.21", + "@swc/core-darwin-x64": "1.11.21", + "@swc/core-linux-arm-gnueabihf": "1.11.21", + "@swc/core-linux-arm64-gnu": "1.11.21", + "@swc/core-linux-arm64-musl": "1.11.21", + "@swc/core-linux-x64-gnu": "1.11.21", + "@swc/core-linux-x64-musl": "1.11.21", + "@swc/core-win32-arm64-msvc": "1.11.21", + "@swc/core-win32-ia32-msvc": "1.11.21", + "@swc/core-win32-x64-msvc": "1.11.21" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.21.tgz", + "integrity": "sha512-v6gjw9YFWvKulCw3ZA1dY+LGMafYzJksm1mD4UZFZ9b36CyHFowYVYug1ajYRIRqEvvfIhHUNV660zTLoVFR8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.21.tgz", + "integrity": "sha512-CUiTiqKlzskwswrx9Ve5NhNoab30L1/ScOfQwr1duvNlFvarC8fvQSgdtpw2Zh3MfnfNPpyLZnYg7ah4kbT9JQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.21.tgz", + "integrity": "sha512-YyBTAFM/QPqt1PscD8hDmCLnqPGKmUZpqeE25HXY8OLjl2MUs8+O4KjwPZZ+OGxpdTbwuWFyMoxjcLy80JODvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.21.tgz", + "integrity": "sha512-DQD+ooJmwpNsh4acrftdkuwl5LNxxg8U4+C/RJNDd7m5FP9Wo4c0URi5U0a9Vk/6sQNh9aSGcYChDpqCDWEcBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.21.tgz", + "integrity": "sha512-y1L49+snt1a1gLTYPY641slqy55QotPdtRK9Y6jMi4JBQyZwxC8swWYlQWb+MyILwxA614fi62SCNZNznB3XSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.21.tgz", + "integrity": "sha512-NesdBXv4CvVEaFUlqKj+GA4jJMNUzK2NtKOrUNEtTbXaVyNiXjFCSaDajMTedEB0jTAd9ybB0aBvwhgkJUWkWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.21.tgz", + "integrity": "sha512-qFV60pwpKVOdmX67wqQzgtSrUGWX9Cibnp1CXyqZ9Mmt8UyYGvmGu7p6PMbTyX7vdpVUvWVRf8DzrW2//wmVHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.21.tgz", + "integrity": "sha512-DJJe9k6gXR/15ZZVLv1SKhXkFst8lYCeZRNHH99SlBodvu4slhh/MKQ6YCixINRhCwliHrpXPym8/5fOq8b7Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.21.tgz", + "integrity": "sha512-TqEXuy6wedId7bMwLIr9byds+mKsaXVHctTN88R1UIBPwJA92Pdk0uxDgip0pEFzHB/ugU27g6d8cwUH3h2eIw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.21.tgz", + "integrity": "sha512-BT9BNNbMxdpUM1PPAkYtviaV0A8QcXttjs2MDtOeSqqvSJaPtyM+Fof2/+xSwQDmDEFzbGCcn75M5+xy3lGqpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.9.0.tgz", + "integrity": "sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/core": "^1.11.21" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.0.tgz", + "integrity": "sha512-MsBdObhM4cEwkzCiraDv7A6txFXEqtNXOb877TsSp2FCkBNl8JfVQrmiuDqC1IkejT6JLPzYBXx/xAiYhyzgGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.25.0", + "@eslint/plugin-kit": "^0.2.8", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.19.tgz", + "integrity": "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", + "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.12" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a7565d4 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "collegetransferai", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react-swc": "^3.8.0", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.1" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..9735241 --- /dev/null +++ b/src/App.css @@ -0,0 +1,50 @@ +body { + font-family: Arial, sans-serif; + margin: 20px; +} +.form-group { + margin-bottom: 15px; +} +label { + display: block; + margin-bottom: 5px; +} +input, select, button { + padding: 10px; + width: 100%; + max-width: 400px; + margin-bottom: 10px; +} +button { + background-color: #007BFF; + color: white; + border: none; + cursor: pointer; +} +button:hover { + background-color: #0056b3; +} +.result { + margin-top: 20px; + padding: 10px; + border: 1px solid #ccc; + background-color: #f9f9f9; +} +.dropdown { + position: absolute; + background-color: white; + border: 1px solid #ccc; + max-height: 200px; + overflow-y: auto; + width: 100%; + z-index: 1000; +} + +.dropdown-item { + padding: 10px; + cursor: pointer; +} + +.dropdown-item:hover { + background-color: #f1f1f1; +} \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..380cd7a --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,12 @@ +import React from "react"; +import CollegeTransferAI from "./components/CollegeTransferAI"; + +const App = () => { + return ( +
+ +
+ ); +}; + +export default App; diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/CollegeTransferAI.jsx b/src/components/CollegeTransferAI.jsx new file mode 100644 index 0000000..00e721a --- /dev/null +++ b/src/components/CollegeTransferAI.jsx @@ -0,0 +1,73 @@ +import React from "react"; +import '../App.css'; // Ensure the CSS file is imported +import {hideDropdown,filterAcademicYears,filterInstitutions,filterNonCCs,filterMajors} from '../js/main'; // Import the utility functions +const CollegeTransferAI = () => { + return ( +
+

College Transfer AI

+ + {/* Form to Get All Institutions for Sending Institution */} +
+ + filterInstitutions()} + onFocus={() => filterInstitutions()} + onBlur={() => hideDropdown()} + /> +
+
+ + {/* Form to Get All Institutions for Receiving Institution */} +
+ + filterNonCCs()} + onFocus={() => filterNonCCs()} + onBlur={() => hideDropdown()} + disabled + /> +
+
+
+ + filterAcademicYears()} + onFocus={() => filterAcademicYears()} + onBlur={() => hideDropdown()} + /> +
+
+ + {/* Form to Get Majors */} +
+ + filterMajors()} + onFocus={() => filterMajors()} + onBlur={() => hideDropdown()} + disabled + /> +
+
+
+

Result:

+
No data yet...
+
+
+ ); +}; + + +export default CollegeTransferAI; \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..08a3ac9 --- /dev/null +++ b/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/static/js/main.js b/src/js/main.js similarity index 93% rename from static/js/main.js rename to src/js/main.js index 50591df..1584449 100644 --- a/static/js/main.js +++ b/src/js/main.js @@ -1,4 +1,4 @@ -const backendUrl = "http://127.0.0.1:5000"; +const backendUrl = "http://localhost:5173/"; let institutionsDict = {}; let nonCCS = {}; @@ -6,7 +6,7 @@ let academicYears = {}; let majors = {}; let agreementGenerated = false; -async function populateData(endpoint, targetObj) { +export async function populateData(endpoint, targetObj) { try { const response = await fetch(`${backendUrl}/${endpoint}`); const data = await response.json(); @@ -18,7 +18,7 @@ async function populateData(endpoint, targetObj) { } } -function hideDropdown() { +export function hideDropdown() { setTimeout(() => { const instDropdown = document.getElementById("institutionDropdown"); if (instDropdown) instDropdown.innerHTML = ""; @@ -29,7 +29,7 @@ function hideDropdown() { }, 150); } -function updateMajorsInputState() { +export function updateMajorsInputState() { const sendingInput = document.getElementById("searchInstitution"); const receivingInput = document.getElementById("receivingInstitution"); const academicYearInput = document.getElementById("academicYears"); @@ -50,7 +50,7 @@ function updateMajorsInputState() { } } -function filterDropdown(inputId, dropdownId, dataObj, dataAttr) { +export function filterDropdown(inputId, dropdownId, dataObj, dataAttr) { const searchInput = document.getElementById(inputId).value.toLowerCase(); const dropdown = document.getElementById(dropdownId); if (!dropdown) return; @@ -111,7 +111,7 @@ function filterDropdown(inputId, dropdownId, dataObj, dataAttr) { }); } -function filterInstitutions() { +export function filterInstitutions() { filterDropdown("searchInstitution", "institutionDropdown", institutionsDict, "data-sending-institution-id"); const sendingInput = document.getElementById("searchInstitution"); const receivingInput = document.getElementById("receivingInstitution"); @@ -125,21 +125,21 @@ function filterInstitutions() { updateMajorsInputState(); } -function filterNonCCs() { +export function filterNonCCs() { filterDropdown("receivingInstitution", "receivingInstitutionDropdown", nonCCS, "data-receiving-institution-id"); updateMajorsInputState(); } -function filterAcademicYears() { +export function filterAcademicYears() { filterDropdown("academicYears", "academicYearsDropdown", academicYears, "data-academic-year-id"); updateMajorsInputState(); } -function filterMajors() { +export function filterMajors() { filterDropdown("majors", "majorsDropdown", majors, "data-major-id"); } -async function getInstitutions() { +export async function getInstitutions() { try { const response = await fetch(`${backendUrl}/institutions`); const data = await response.json(); @@ -149,7 +149,7 @@ async function getInstitutions() { } } -async function getAcademicYears() { +export async function getAcademicYears() { try { const response = await fetch(`${backendUrl}/academic-years`); const data = await response.json(); @@ -159,7 +159,7 @@ async function getAcademicYears() { } } -async function getAllMajors() { +export async function getAllMajors() { const sendingInstitutionInput = document.getElementById("sendingInstitutionId"); const receivingInstitutionInput = document.getElementById("receivingInstitutionId"); const academicYearInput = document.getElementById("academicYearId"); @@ -189,7 +189,7 @@ async function getAllMajors() { } } -async function getMajorKey() { +export async function getMajorKey() { const sendingInstitutionInput = document.getElementById("sendingInstitutionId"); const receivingInstitutionInput = document.getElementById("receivingInstitutionId"); const academicYearInput = document.getElementById("academicYearId"); @@ -221,7 +221,7 @@ async function getMajorKey() { } } -function allDependenciesSelected() { +export function allDependenciesSelected() { const sendingInput = document.getElementById("searchInstitution"); const receivingInput = document.getElementById("receivingInstitution"); const academicYearInput = document.getElementById("academicYears"); @@ -232,7 +232,7 @@ function allDependenciesSelected() { ); } -async function getArticulationAgreement(changedField) { +export async function getArticulationAgreement(changedField) { const majorsInput = document.getElementById("majors"); const key = majorsInput.getAttribute("data-major-key"); if (!key) { @@ -271,7 +271,7 @@ async function getArticulationAgreement(changedField) { } } -function showLoadingAgreement() { +export function showLoadingAgreement() { const resultContent = document.getElementById("resultContent"); resultContent.textContent = "Loading Agreement..."; } @@ -283,7 +283,7 @@ window.onload = function () { }; -function displayResult(data) { +export function displayResult(data) { const resultContent = document.getElementById("resultContent"); if (data.pdf_filename) { window.open(`/pdf/${data.pdf_filename}`, '_blank'); @@ -295,7 +295,7 @@ function displayResult(data) { } } -function resetAllFields() { +export function resetAllFields() { document.getElementById("searchInstitution").value = ""; document.getElementById("searchInstitution").removeAttribute("data-sending-institution-id"); document.getElementById("receivingInstitution").value = ""; diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..b9a1a6d --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 095301f..0000000 --- a/templates/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - College Transfer AI - - - -

College Transfer AI

- - -
- - - -
- - -
- - - -
- - -
- - - -
- - -
- - - -
- - - - - - -
-

Result:

-
No data yet...
-
- - - - \ No newline at end of file diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..2328e17 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) From a7da63f377a8785bda35cec86bb7b52b36e9bb40 Mon Sep 17 00:00:00 2001 From: Ahmon Date: Sun, 20 Apr 2025 01:07:02 -0700 Subject: [PATCH 4/5] [fix] backend not connecting to react components --- .../__pycache__/app.cpython-313.pyc | Bin 5571 -> 5566 bytes .../college_transfer_API.cpython-313.pyc | Bin 8236 -> 8205 bytes college_transfer_ai/app.py | 2 +- index.html | 2 +- package-lock.json | 2 +- package.json | 2 +- src/js/main.js | 6 +++--- vite.config.js | 3 +++ 8 files changed, 10 insertions(+), 7 deletions(-) diff --git a/college_transfer_ai/__pycache__/app.cpython-313.pyc b/college_transfer_ai/__pycache__/app.cpython-313.pyc index f80a74b677f52748d6c8710b39175e2ddd68b792..388191b1dd9f39708945f395bba310f4baa570cd 100644 GIT binary patch delta 121 zcmX@Cy-%C>GcPX}0}#Ai%93$oBkwb2dqyCS8Hhi#0Ewv#>5Q68epSlO`8heM>8T1K zMTvREX{kjDj-CpR0iFt(#R^5Gd3l+6>3S;}ii8*#ChN20aIylmHZVNkHrTv|MOuK{ U@G~<5lfVZiMn=QQmqb1S0NFAjIsgCw delta 106 zcmdm|eOR0KGcPX}0}vz&v1DA>$oq_$iy6oR;m;D2YXl@ERTwmx{HnMz^HNeP^fF3v zb2J%?*nrY2CmXTk2=W414Gd4XrDoV%=GM5tqOo~7i?jf@=4WOGCV>x3jEtI-&x(8m E0B18BAOHXW diff --git a/college_transfer_ai/__pycache__/college_transfer_API.cpython-313.pyc b/college_transfer_ai/__pycache__/college_transfer_API.cpython-313.pyc index f94fa1d60e5019acfbbfacc3d8d49adf8227cd23..b3812dbcd7155b782c88d6988ae28345a3e4b56f 100644 GIT binary patch delta 1339 zcmaKsO>7fK6o7ZUUjMDvPHe}qT{}OG5pi1_hk_s~LV*P64|UZTL;qmqINPx4dN;fs z3z=3#xqw7fp>~kC^wQiSxFFwpXfKs|LkQF!hN4#O0f|$a7WLYBYe)&A9cjP$d2il( zGw;n_pZ;uG`%}{*l4!Lx|LMl(noCMIR*%Uf!hV$Z5|zdMXGoN-`Zq|tOO*#?Y*6Sz z29Xosk>d@kZ5#q{fGf*j+7$9^HJDaaR4E|}Y%MrPqTPqV>pp!PSJH?iVgQj6COp(?%qsjgluO&B`Yg0piwU;FN)E+@}Cz{3iTq}8URm$ z;kGU6U4`CsEX!Cl+}@q2rtMObwyEjZfY#lRz{sNpQ8D4{(^Q$9?|z-?lG*KI0p7*c zgz3&WtybGMX=kQq6Sf8l?+LwUgv4&7vjs2jAE32ocxCs~6>^1T2EQju-CqVLN$&l) zQ1#mGN4-z7BlXB!ADhf3hp{N1YBjYMog%hcSLX)USK%BxnaxD&;hI*9P5DNox_BMg zRHPQK^$9JluCi~llWZ-U44=^IO08e$uv?jJY%(_%NmRLCr;ZLgx~Fm<6ZYG4Q;9|4 zt`vhQ(ZPO%pOy1PAIQwiSN6nudGP9DvrKr{D@k3{HsE$1Xj-OWQ|?=s=e`eJ@#22S zkEUWm^f&>%9->SwR0G$9zeNwEzb`M=lj~>X*7Ul1Xk=dO=2n&sBi9nUz z9(qOB#0-jvf1`X0r^gW|5%Y)@0Y0GXE^Sb=sW&JEbG}UtSJ&4iz>D|l00KBd;OADD zH9WeKSH?H?rIg~vODSdSVflF_)7yY5BK7!h;K^mH(K!p|;%QoTJGSt!Y=VrE;Bu$s z*ozMM#SB^q+{k6rxPa45?>w}x7fK6vubGUa#%-*op1w7>W2w236q*cGi;Kk9XX~1AmlwjhF+CR2zNK+o32V8JaIr25CaTwUP2$HvhWnu!07n%$@I2~<)Q zH83Y1_Zt43Q1aS1+>ZKn3CVV)Y~sTfk}gXBO8+IyQV2FsEf;X3qQVuX$j4*8?=%w4g~ z3Mq8Y41SIL#KlvH$U*P#m=3S-0q?2(;QSCE(M)PM<~{+P*}zEV`rtf+;`UKz9Bb=gg=G)ltN6F%D@weUn;y#(cU(b9VkyG?q=7W5Y&&UYi z|1+G1=_249pa$qrZ#JFmgCVpPkzc{X2i62VnBBRWl}6VOp{O*xz9%XTZ4`#21aUGT zn75MiZ$lu_&B5emyI~s3CXv8p03eWwa97{Kbh8}u9%z_LExmq_T;>ai|14 - + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 862eadd..fd35c87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", - "vite": "^6.3.1" + "vite": "^6.3.2" } }, "node_modules/@esbuild/aix-ppc64": { diff --git a/package.json b/package.json index a7565d4..72459e3 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,6 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", - "vite": "^6.3.1" + "vite": "^6.3.2" } } diff --git a/src/js/main.js b/src/js/main.js index 1584449..1a89456 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,4 +1,4 @@ -const backendUrl = "http://localhost:5173/"; +const backendUrl = "http://localhost:5000"; let institutionsDict = {}; let nonCCS = {}; @@ -10,7 +10,7 @@ export async function populateData(endpoint, targetObj) { try { const response = await fetch(`${backendUrl}/${endpoint}`); const data = await response.json(); - + Object.assign(targetObj, data); } catch (error) { console.error(`Error fetching ${endpoint}:`, error); @@ -286,7 +286,7 @@ window.onload = function () { export function displayResult(data) { const resultContent = document.getElementById("resultContent"); if (data.pdf_filename) { - window.open(`/pdf/${data.pdf_filename}`, '_blank'); + window.open(`${backendUrl}/pdf/${data.pdf_filename}`, '_blank'); resultContent.textContent = "PDF opened in a new tab."; agreementGenerated = true; // Set flag resetAllFields(); // Reset fields right after generating agreement diff --git a/vite.config.js b/vite.config.js index 2328e17..d8d8c9a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,4 +4,7 @@ import react from '@vitejs/plugin-react-swc' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + server: { + port: 5173 + } }) From e84fe9289878ff1745feee379d504d10f303c9a6 Mon Sep 17 00:00:00 2001 From: Ahmon Date: Mon, 21 Apr 2025 00:06:05 -0700 Subject: [PATCH 5/5] feat: Add AI chat functionality and fix API proxy calls - **Chat Feature:** - Added `ChatInterface.jsx` component. - Added `/chat` endpoint in Flask (`app.py`) integrating OpenAI GPT-4 Vision. - Updated `AgreementViewerPage.jsx` with 3-column layout including chat. - Refactored state management for image filenames in `AgreementViewerPage.jsx`. - Simplified `PdfViewer.jsx`. - **API/Proxy Fix:** - Corrected `fetchData` in `api.js` to pass `options` object to `fetch`. - Ensured `fetchData` uses relative `/api/` paths for Vite proxy. - Corrected `fetchData` usage in `ChatInterface.jsx`. - **Config:** - Added cache patterns to `.gitignore`. - Configured `MAX_CONTENT_LENGTH` in Flask. - Added debug logging to Vite proxy. --- .github/workflows/python-app.yml | 34 ++ .gitignore | 18 + backend/college_transfer_ai/app.py | 277 ++++++++++++++++ .../college_transfer_API.py | 5 +- {tests => backend/tests}/test_app.py | 2 +- .../__pycache__/__init__.cpython-313.pyc | Bin 186 -> 0 bytes .../__pycache__/app.cpython-313.pyc | Bin 5771 -> 0 bytes .../__pycache__/app.cpython-38.pyc | Bin 3290 -> 0 bytes .../college_transfer_API.cpython-313.pyc | Bin 8633 -> 0 bytes .../college_transfer_API.cpython-38.pyc | Bin 5281 -> 0 bytes college_transfer_ai/app.py | 99 ------ index.html | 2 +- package-lock.json | 44 ++- package.json | 3 +- public/vite.svg | 1 - requirements.txt | 3 +- src/App.css | 118 +++++-- src/App.jsx | 24 +- src/assets/react.svg | 1 - src/components/AgreementViewerPage.jsx | 173 ++++++++++ src/components/ChatInterface.jsx | 99 ++++++ src/components/CollegeTransferAI.jsx | 73 ---- src/components/CollegeTransferForm.jsx | 295 +++++++++++++++++ src/components/PdfViewer.jsx | 47 +++ src/index.css | 68 ---- src/js/main.js | 313 ------------------ src/main.jsx | 13 +- src/services/api.js | 65 ++++ static/css/styles.css | 50 --- .../test_app.cpython-313-pytest-8.3.5.pyc | Bin 9615 -> 0 bytes .../test_app.cpython-38-pytest-8.3.5.pyc | Bin 3959 -> 0 bytes .../test_e2e.cpython-313-pytest-8.3.5.pyc | Bin 2424 -> 0 bytes vite.config.js | 25 +- 33 files changed, 1207 insertions(+), 645 deletions(-) create mode 100644 .github/workflows/python-app.yml create mode 100644 backend/college_transfer_ai/app.py rename {college_transfer_ai => backend/college_transfer_ai}/college_transfer_API.py (97%) rename {tests => backend/tests}/test_app.py (96%) delete mode 100644 college_transfer_ai/__pycache__/__init__.cpython-313.pyc delete mode 100644 college_transfer_ai/__pycache__/app.cpython-313.pyc delete mode 100644 college_transfer_ai/__pycache__/app.cpython-38.pyc delete mode 100644 college_transfer_ai/__pycache__/college_transfer_API.cpython-313.pyc delete mode 100644 college_transfer_ai/__pycache__/college_transfer_API.cpython-38.pyc delete mode 100644 college_transfer_ai/app.py delete mode 100644 public/vite.svg delete mode 100644 src/assets/react.svg create mode 100644 src/components/AgreementViewerPage.jsx create mode 100644 src/components/ChatInterface.jsx delete mode 100644 src/components/CollegeTransferAI.jsx create mode 100644 src/components/CollegeTransferForm.jsx create mode 100644 src/components/PdfViewer.jsx delete mode 100644 src/index.css delete mode 100644 src/js/main.js create mode 100644 src/services/api.js delete mode 100644 static/css/styles.css delete mode 100644 tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc delete mode 100644 tests/__pycache__/test_app.cpython-38-pytest-8.3.5.pyc delete mode 100644 tests/__pycache__/test_e2e.cpython-313-pytest-8.3.5.pyc diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..e2a6d8b --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,34 @@ +name: Python application + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13.2' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Playwright browsers + run: | + python -m playwright install --with-deps + + - name: Run tests + run: | + pytest \ No newline at end of file diff --git a/.gitignore b/.gitignore index a547bf3..a9b9c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,17 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* +# Node/Frontend Dependencies and Build Output node_modules dist dist-ssr *.local +# Environment Variables +env +.env* +!.env.example + # Editor directories and files .vscode/* !.vscode/extensions.json @@ -22,3 +28,15 @@ dist-ssr *.njsproj *.sln *.sw? + +# Python Caches and Bytecode +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ + +# General Caches +.cache/ diff --git a/backend/college_transfer_ai/app.py b/backend/college_transfer_ai/app.py new file mode 100644 index 0000000..e498595 --- /dev/null +++ b/backend/college_transfer_ai/app.py @@ -0,0 +1,277 @@ +import os +from flask import Flask, jsonify, request, Response +from flask_cors import CORS +from backend.college_transfer_ai.college_transfer_API import CollegeTransferAPI +import json +import gridfs +from pymongo import MongoClient +import fitz # Import PyMuPDF +import base64 # Needed for image encoding +from openai import OpenAI # Import OpenAI library +from dotenv import load_dotenv # To load environment variables + +print("--- Flask app.py loading ---") + +# Load environment variables from .env file +load_dotenv() + +# --- OpenAI Client Setup --- +# Ensure you have OPENAI_API_KEY set in your .env file or environment variables +openai_api_key = os.getenv("OPENAI_API_KEY") +if not openai_api_key: + print("Warning: OPENAI_API_KEY environment variable not set.") + # Optionally, raise an error or handle appropriately + # raise ValueError("OPENAI_API_KEY environment variable not set.") +openai_client = OpenAI(api_key=openai_api_key) +# --- End OpenAI Setup --- + + +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +app = Flask( + __name__, + template_folder=os.path.join(BASE_DIR, 'templates'), + static_folder=os.path.join(BASE_DIR, 'static') +) +CORS(app) + +# --- Set Max Request Size --- +# Example: Limit request size to 16 megabytes +app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 +# --- End Max Request Size --- + +api = CollegeTransferAPI() + +# --- MongoDB Setup --- +MONGO_URI = os.getenv("MONGO_URI") # Use env var or default +client = MongoClient(MONGO_URI) +db = client["CollegeTransferAICluster"] # Consider using a specific DB name from env var if needed +fs = gridfs.GridFS(db) +# --- End MongoDB Setup --- + +@app.route('/') +def home(): + return "College Transfer AI API is running." + +# Endpoint to get all institutions +@app.route('/institutions', methods=['GET']) +def get_institutions(): + try: + institutions = api.get_sending_institutions() + return jsonify(institutions) + except Exception as e: + print(f"Error in /institutions: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get receiving institutions +@app.route('/receiving-institutions', methods=['GET']) +def get_receiving_institutions(): + sending_institution_id = request.args.get('sendingInstitutionId') + if not sending_institution_id: + return jsonify({"error": "Missing sendingInstitutionId parameter"}), 400 + try: + non_ccs = api.get_receiving_institutions(sending_institution_id) + return jsonify(non_ccs) + except Exception as e: + print(f"Error in /receiving-institutions: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get academic years +@app.route('/academic-years', methods=['GET']) +def get_academic_years(): + try: + academic_years = api.get_academic_years() + return jsonify(academic_years) + except Exception as e: + print(f"Error in /academic-years: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get majors +@app.route('/majors', methods=['GET']) +def get_all_majors(): + sending_institution_id = request.args.get('sendingInstitutionId') + receiving_institution_id = request.args.get('receivingInstitutionId') + academic_year_id = request.args.get('academicYearId') + category_code = request.args.get('categoryCode') + if not all([sending_institution_id, receiving_institution_id, academic_year_id, category_code]): + return jsonify({"error": "Missing required parameters (sendingInstitutionId, receivingInstitutionId, academicYearId, categoryCode)"}), 400 + try: + majors = api.get_all_majors(sending_institution_id, receiving_institution_id, academic_year_id, category_code) + return jsonify(majors) + except Exception as e: + print(f"Error in /majors: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get articulation agreement PDF filename +@app.route('/articulation-agreement', methods=['GET']) +def get_articulation_agreement(): + key = request.args.get("key") + if not key: + return jsonify({"error": "Missing key parameter"}), 400 + try: + keyArray = key.split("/") + if len(keyArray) < 4: + return jsonify({"error": "Invalid key format"}), 400 + sending_institution_id = int(keyArray[1]) + receiving_institution_id = int(keyArray[3]) + academic_year_id = int(keyArray[0]) + pdf_filename = api.get_articulation_agreement(academic_year_id, sending_institution_id, receiving_institution_id, key) + return jsonify({"pdf_filename": pdf_filename}) + except ValueError: + return jsonify({"error": "Invalid numeric value in key"}), 400 + except Exception as e: + print(f"Error in /articulation-agreement: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get image filenames for a PDF (extracts if needed) +@app.route('/pdf-images/') +def get_pdf_images(filename): + try: + pdf_file = fs.find_one({"filename": filename}) + if not pdf_file: + return jsonify({"error": "PDF not found"}), 404 + + pdf_bytes = pdf_file.read() + doc = fitz.open("pdf", pdf_bytes) + image_filenames = [] + + # Check cache + first_image_name = f"{filename}_page_0.png" + if fs.exists({"filename": first_image_name}): + for page_num in range(len(doc)): + img_filename = f"{filename}_page_{page_num}.png" + # Verify each image exists, not just the first + if fs.exists({"filename": img_filename}): + image_filenames.append(img_filename) + else: + # If one is missing, break and regenerate all (or handle differently) + print(f"Cache incomplete, image {img_filename} missing. Regenerating.") + image_filenames = [] # Reset + break + if image_filenames: # If loop completed without break + print(f"All images for {filename} found in cache.") + doc.close() + return jsonify({"image_filenames": image_filenames}) + + # If not fully cached, extract/save + print(f"Generating images for {filename}...") + image_filenames = [] # Ensure it's empty before regenerating + for page_num in range(len(doc)): + page = doc.load_page(page_num) + pix = page.get_pixmap(dpi=150) + img_bytes = pix.tobytes("png") + img_filename = f"{filename}_page_{page_num}.png" + + # Delete existing before putting new one (optional, ensures overwrite) + existing_file = fs.find_one({"filename": img_filename}) + if existing_file: + fs.delete(existing_file._id) + + fs.put(img_bytes, filename=img_filename, contentType='image/png') + image_filenames.append(img_filename) + print(f"Saved image {img_filename}") + + doc.close() + return jsonify({"image_filenames": image_filenames}) + + except Exception as e: + print(f"Error extracting images for {filename}: {e}") + return jsonify({"error": f"Failed to extract images: {str(e)}"}), 500 + +# Endpoint to serve a single image +@app.route('/image/') +def serve_image(image_filename): + try: + grid_out = fs.find_one({"filename": image_filename}) + if not grid_out: + return "Image not found", 404 + image_data = grid_out.read() + # Use content type from GridFS if available, default to image/png + response_mimetype = getattr(grid_out, 'contentType', 'image/png') + response = Response(image_data, mimetype=response_mimetype) + return response + except Exception as e: + print(f"Error serving image {image_filename}: {e}") + return jsonify({"error": f"Failed to serve image: {str(e)}"}), 500 + +# --- NEW: Chat Endpoint --- +@app.route('/chat', methods=['POST']) +def chat_with_agreement(): + if not openai_client: + return jsonify({"error": "OpenAI client not configured. Check API key."}), 500 + + try: + data = request.get_json() + if not data: + return jsonify({"error": "Invalid JSON payload"}), 400 + + user_message = data.get('message') + image_filenames = data.get('image_filenames') + + if not user_message or not image_filenames: + return jsonify({"error": "Missing 'message' or 'image_filenames' in request"}), 400 + + if not isinstance(image_filenames, list): + return jsonify({"error": "'image_filenames' must be a list"}), 400 + + print(f"Received chat request: '{user_message}' with {len(image_filenames)} images.") + + # Prepare message content for OpenAI API (multimodal) + openai_message_content = [{"type": "text", "text": user_message}] + image_count = 0 + for filename in image_filenames: + try: + grid_out = fs.find_one({"filename": filename}) + if not grid_out: + print(f"Warning: Image '{filename}' not found in GridFS. Skipping.") + continue # Skip this image + + image_data = grid_out.read() + base64_image = base64.b64encode(image_data).decode('utf-8') + openai_message_content.append({ + "type": "image_url", + "image_url": { + # Ensure correct mime type if not always PNG + "url": f"data:{getattr(grid_out, 'contentType', 'image/png')};base64,{base64_image}" + } + }) + image_count += 1 + except Exception as img_err: + print(f"Error reading/encoding image {filename}: {img_err}. Skipping.") + # Optionally return an error if images are critical + # return jsonify({"error": f"Failed to process image {filename}: {img_err}"}), 500 + + if image_count == 0: + return jsonify({"error": "No valid images found or processed for context."}), 400 + + # Call OpenAI API + print(f"Sending request to OpenAI with text and {image_count} images...") + try: + chat_completion = openai_client.chat.completions.create( + model="gpt-4o-mini", # Use the appropriate vision model + messages=[ + { + "role": "user", + "content": openai_message_content, + } + ], + max_tokens=1000 # Adjust as needed + ) + + # Extract the reply + reply = chat_completion.choices[0].message.content + print(f"Received reply from OpenAI: '{reply[:100]}...'") # Log snippet + return jsonify({"reply": reply}) + + except Exception as openai_err: + print(f"OpenAI API error: {openai_err}") + return jsonify({"error": f"OpenAI API error: {str(openai_err)}"}), 500 + + except Exception as e: + print(f"Error in /chat endpoint: {e}") + return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500 +# --- End Chat Endpoint --- + +if __name__ == '__main__': + # Use host='0.0.0.0' to be accessible on the network if needed + # Use debug=False in production + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/college_transfer_ai/college_transfer_API.py b/backend/college_transfer_ai/college_transfer_API.py similarity index 97% rename from college_transfer_ai/college_transfer_API.py rename to backend/college_transfer_ai/college_transfer_API.py index 3edc792..16a4a56 100644 --- a/college_transfer_ai/college_transfer_API.py +++ b/backend/college_transfer_ai/college_transfer_API.py @@ -3,6 +3,7 @@ from pymongo import MongoClient import gridfs import json +import os class CollegeTransferAPI: def __init__(self): @@ -181,7 +182,9 @@ def get_articulation_agreement(self, academic_year_id, sending_institution_id, r f"{self.get_year_from_id(academic_year_id)}.pdf" ) - client = MongoClient("mongodb+srv://ahmonembaye:WCpjfEgNcIomkBcN@collegetransferaicluste.vlsybad.mongodb.net/?retryWrites=true&w=majority&appName=CollegeTransferAICluster") + MONGO_URI = os.getenv("MONGO_URI") + + client = MongoClient(MONGO_URI) db = client["CollegeTransferAICluster"] fs = gridfs.GridFS(db) diff --git a/tests/test_app.py b/backend/tests/test_app.py similarity index 96% rename from tests/test_app.py rename to backend/tests/test_app.py index 3c376ad..c7ec675 100644 --- a/tests/test_app.py +++ b/backend/tests/test_app.py @@ -3,7 +3,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) import pytest -from college_transfer_ai.app import app +from backend.college_transfer_ai.app import app @pytest.fixture def client(): diff --git a/college_transfer_ai/__pycache__/__init__.cpython-313.pyc b/college_transfer_ai/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index c3e011c55415b55ac383e76ec2eafdcc05caaeb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 186 zcmey&%ge<81UmgJ86f&Gh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABy}s&*(xTqIJKxa zCNICFBtOO_wK%&ZzW`1L6y;~7CYKb)IOpf&q^75a6eZ>rr==D-dd4I}MB+=JLh*^2 sG4b)4d6^~g@p=W7w>WHa^HWN5QtgUZfi{6$QVe2zWM*V!EMf+-0HH`SNdN!< diff --git a/college_transfer_ai/__pycache__/app.cpython-313.pyc b/college_transfer_ai/__pycache__/app.cpython-313.pyc deleted file mode 100644 index 7eafa83968883ed02654ea56e21805b443a0f91f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5771 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 diff --git a/college_transfer_ai/__pycache__/app.cpython-38.pyc b/college_transfer_ai/__pycache__/app.cpython-38.pyc deleted file mode 100644 index 9c0728e1c1735f40afca47740e100880ebcbd01e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3290 zcmc&$OK%%T67KGKa7dA&EZch7mSra!gu93s?9s5ZwrM*MkSKxWz{xgvZ-)z|g-rcx;pcp9;N zEGvZk8wc}G0R|7DslQV~IN>xVK7LtD6XvtT@~y=7?Zok&#P!{z;1?*)Td|iE{i31m zxRfmUONOR#*)N-ODt-mNPQ08{{VFAboU9D5n10ptSH-mvKjH;k|FD~5l= z@K)|qP@U$56P2Y=tCts~_`WNY&Z|mMT%Bx!w5c`2l=a8(AESws;}KRJDYB^z|Na zm%hx>cGid^k?KC%-OY>BlU4329qOoAV}5nY3|d(XzxONSsmbs+T5qD{^ACeT1Df)n z)1cpL)R67>OA#pW!|t(8;u@6>%>GK`GJH(;Tc>gF@EawhI&6kA&JMpy#SvQ6`O_C*u0kU8amLjJb#WWIkx}+5oE9^vmxh-@sQB6q;ew`Pe{?HU16gsz*mH=LPUXh%NcgB`g+_8d(%&%Dot}=0f zG3wD0U7>Ol)hg7}ENwRb=h{m|r{>7(DDD!93vvV?h*4Rt0b8Qbplo3}vw_UnwKAH@ zE#U1yTT~AD{c1gIhFm03^OK$k<^Na7Ra9~+PhjMEU)SY@1(&IO15~SBqB8OU&m=s_ z7F2a%W_FwZ2?I?|=n!t}h{Amy(-4%;>4dg`W)s>5+UjpUVsd>RmSH@n7^ZoUDC?RZj zAw578_#|vgArc7h7h)DB_8H1U7+iufAU?GC7;A`B+}ea#23R9)8`?6oV`#g-t(~De zuo3kE0o(yu?hYJ`Z<7e<0rtND_O8Lczze!Ka3|P%3)s7_+zZ$j_jBu1^sr1JZy^Sf zw;1mvQhB+Ac|l@_~a zvFaU#e0OrxZYp&BZxS4+_fke$s0Uhh#Yg{mU~YA!dmlm8-3OE&yqC2b=0NFh{LxJA zO>I+S{mR!*p6#TW-f3lB2nauevHDdwJC4Ae;NGJ#Yt;9W2vT;hBWkY1`<}aP8S$3# z-Ou6s?7)XJp?(6rCAvgzOA&HNH5fBb7flbCxOJX<@A=v5FX9ImFo%MiOJOk;^Zc zMGzsRl2}psA5Q?8uV{AP<3W)pi?BSmv67U3#+`*u&ww?yhusPFab5_Ilp*YsEK2hN zl#=L5C^hOF1itt9(ZSQ;$=-A1;am`44bm}z(syLm)xuOxrc^MgR$`Ko7?aEeAKSr_ z-E_``CPSMt#Jitlyc>(p@Y<{U(0O#n1UNKinQc=BFF4MBUB{y?n7Cvw(~>vWsz6@> Iez8P<2RThCtpET3 diff --git a/college_transfer_ai/__pycache__/college_transfer_API.cpython-313.pyc b/college_transfer_ai/__pycache__/college_transfer_API.cpython-313.pyc deleted file mode 100644 index e7398367117afc41dae48aae6b9e8160e7d6c714..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8633 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 o)sbE%!56') -def serve_pdf(filename): - client = MongoClient("mongodb+srv://ahmonembaye:WCpjfEgNcIomkBcN@collegetransferaicluste.vlsybad.mongodb.net/?retryWrites=true&w=majority&appName=CollegeTransferAICluster") - db = client["CollegeTransferAICluster"] - fs = gridfs.GridFS(db) - file = fs.find_one({"filename": filename}) - if not file: - return "PDF not found", 404 - return Response(file.read(), mimetype='application/pdf') - -if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file diff --git a/index.html b/index.html index 7678321..d225255 100644 --- a/index.html +++ b/index.html @@ -2,6 +2,6 @@
- + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fd35c87..c6e812b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.0", "dependencies": { "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-router-dom": "^6.30.0" }, "devDependencies": { "@eslint/js": "^9.22.0", @@ -665,6 +666,15 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.40.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", @@ -2137,6 +2147,38 @@ "react": "^19.1.0" } }, + "node_modules/react-router": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", diff --git a/package.json b/package.json index 72459e3..26c88ae 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ }, "dependencies": { "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-router-dom": "^6.30.0" }, "devDependencies": { "@eslint/js": "^9.22.0", diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1c04822..8b3d6b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ requests pymongo playwright jinja2 -pytest \ No newline at end of file +pytest +PyMuPDF \ No newline at end of file diff --git a/src/App.css b/src/App.css index 9735241..38ec24c 100644 --- a/src/App.css +++ b/src/App.css @@ -1,50 +1,130 @@ +/* Reset and Base Styles */ body { - font-family: Arial, sans-serif; - margin: 20px; + font-family: Arial, Helvetica, sans-serif; /* Common sans-serif font */ + margin: 0; + padding: 0; + background-color: #f0f0f0; /* Light gray background like assist.org */ + color: #333; /* Standard dark text color */ + line-height: 1.6; } + +/* Container for centering content */ +#root > div { /* Target the main div rendered by React */ + max-width: 960px; /* Max width similar to assist.org content area */ + margin: 20px auto; /* Center the container */ + padding: 20px; + background-color: #fff; /* White background for content area */ + border: 1px solid #ccc; /* Subtle border */ + box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* Slight shadow */ +} + +h1, h2, h3 { + color: #003366; /* Dark blue for headings */ + margin-top: 0; +} + +/* Form Styling */ .form-group { - margin-bottom: 15px; + margin-bottom: 1rem; + position: relative; /* Needed for absolute positioning of dropdown */ } + label { display: block; - margin-bottom: 5px; + margin-bottom: 0.5rem; + font-weight: bold; + color: #555; } -input, select, button { - padding: 10px; - width: 100%; - max-width: 400px; - margin-bottom: 10px; + +input[type="text"], +select { /* Style both text inputs and selects similarly */ + padding: 8px 12px; + width: 100%; /* Full width within the container */ + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; /* Include padding and border in width */ + font-size: 1rem; +} + +input[type="text"]:disabled { + background-color: #e9ecef; /* Gray out disabled inputs */ + cursor: not-allowed; } + +/* Button Styling */ button { - background-color: #007BFF; + background-color: #005ea2; /* Assist.org primary blue */ color: white; border: none; + padding: 10px 20px; + border-radius: 4px; cursor: pointer; + font-size: 1rem; + transition: background-color 0.2s ease; + display: inline-block; /* Allow setting width if needed, but default to content size */ + width: auto; /* Override previous 100% width */ + margin-bottom: 0; /* Remove default margin */ } + button:hover { - background-color: #0056b3; + background-color: #003366; /* Darker blue on hover */ } + +button:disabled { + background-color: #a0a0a0; /* Gray out disabled button */ + cursor: not-allowed; +} + +/* Result Area */ .result { - margin-top: 20px; - padding: 10px; - border: 1px solid #ccc; - background-color: #f9f9f9; + margin-top: 25px; + padding: 15px; + border: 1px solid #ddd; + background-color: #f8f8f8; + border-radius: 4px; +} + +.result h3 { + margin-top: 0; + color: #555; } + +/* Dropdown Styling */ .dropdown { position: absolute; background-color: white; border: 1px solid #ccc; - max-height: 200px; + border-top: none; /* Attach visually to input */ + max-height: 250px; overflow-y: auto; - width: 100%; + width: 100%; /* Match input width */ + box-sizing: border-box; z-index: 1000; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + border-radius: 0 0 4px 4px; /* Rounded bottom corners */ } .dropdown-item { - padding: 10px; + padding: 8px 12px; cursor: pointer; + font-size: 0.95rem; + border-bottom: 1px solid #eee; /* Separator lines */ +} +.dropdown-item:last-child { + border-bottom: none; } .dropdown-item:hover { - background-color: #f1f1f1; + background-color: #e9f5ff; /* Light blue hover */ + color: #005ea2; +} + +/* Link Styling (e.g., Back to Form in PdfViewer) */ +a { + color: #005ea2; + text-decoration: none; +} + +a:hover { + text-decoration: underline; } \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 380cd7a..5c2f28e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,22 @@ -import React from "react"; -import CollegeTransferAI from "./components/CollegeTransferAI"; +import React from 'react'; +import { Routes, Route } from 'react-router-dom'; +import CollegeTransferForm from './components/CollegeTransferForm'; +import AgreementViewerPage from './components/AgreementViewerPage'; // Import the new combined page -const App = () => { +function App() { return ( -
- -
+ + {/* Route for the main form */} + } /> + + {/* Route for the combined Agreement Viewer */} + } + /> + + ); -}; +} export default App; diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/AgreementViewerPage.jsx b/src/components/AgreementViewerPage.jsx new file mode 100644 index 0000000..e543c60 --- /dev/null +++ b/src/components/AgreementViewerPage.jsx @@ -0,0 +1,173 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { fetchData } from '../services/api'; +import PdfViewer from './PdfViewer'; +import ChatInterface from './ChatInterface'; // Import ChatInterface +import '../App.css'; + +function AgreementViewerPage() { + const { sendingId, receivingId, yearId } = useParams(); + + // State for this page + const [majors, setMajors] = useState({}); + const [isLoadingMajors, setIsLoadingMajors] = useState(true); + const [error, setError] = useState(null); // General/Major loading error + const [pdfError, setPdfError] = useState(null); // Specific PDF loading error + const [selectedMajorKey, setSelectedMajorKey] = useState(null); + const [selectedMajorName, setSelectedMajorName] = useState(''); // Store name for chat context + const [selectedPdfFilename, setSelectedPdfFilename] = useState(null); + const [imageFilenames, setImageFilenames] = useState([]); // State for image filenames + const [isLoadingPdf, setIsLoadingPdf] = useState(false); // Loading PDF info + images + const [majorSearchTerm, setMajorSearchTerm] = useState(''); + + // Fetch majors + useEffect(() => { + // ... existing useEffect logic to fetch majors ... + if (!sendingId || !receivingId || !yearId) { + setError("Required institution or year information is missing in URL."); + setIsLoadingMajors(false); + return; + } + setIsLoadingMajors(true); + setError(null); + fetchData(`majors?sendingInstitutionId=${sendingId}&receivingInstitutionId=${receivingId}&academicYearId=${yearId}&categoryCode=major`) + .then(data => { + if (Object.keys(data).length === 0) { + setError("No majors found for the selected combination."); + } + setMajors(data); + }) + .catch(err => { + console.error("Error fetching majors:", err); + setError(`Failed to load majors: ${err.message}`); + }) + .finally(() => { + setIsLoadingMajors(false); + }); + }, [sendingId, receivingId, yearId]); + + // Fetch PDF filename AND image filenames when major is selected + const handleMajorSelect = async (majorKey, majorName) => { + if (!majorKey || isLoadingPdf) return; + + setSelectedMajorKey(majorKey); + setSelectedMajorName(majorName); // Store name + setSelectedPdfFilename(null); // Clear previous PDF filename + setImageFilenames([]); // Clear previous images + setIsLoadingPdf(true); + setError(null); // Clear general errors + setPdfError(null); // Clear specific PDF errors + + try { + // 1. Get PDF Filename + const agreementData = await fetchData(`articulation-agreement?key=${majorKey}`); + if (agreementData.pdf_filename) { + const pdfFilename = agreementData.pdf_filename; + setSelectedPdfFilename(pdfFilename); // Set filename for context + + // 2. Get Image Filenames for the PDF + const imageData = await fetchData(`pdf-images/${pdfFilename}`); + if (imageData.image_filenames) { + setImageFilenames(imageData.image_filenames); + } else { + throw new Error(imageData.error || 'Failed to load image list for PDF'); + } + } else if (agreementData.error) { + throw new Error(`Agreement Error: ${agreementData.error}`); + } else { + throw new Error('Received unexpected data when fetching agreement.'); + } + } catch (err) { + console.error("Error fetching agreement or images:", err); + setPdfError(err.message); // Set specific PDF error + setSelectedPdfFilename(null); // Clear filename on error + setImageFilenames([]); // Clear images on error + } finally { + setIsLoadingPdf(false); // Done loading PDF info + images + } + }; + + // Filter majors based on search term + const filteredMajors = useMemo(() => { + // ... existing useMemo logic ... + const lowerCaseSearchTerm = majorSearchTerm.toLowerCase(); + return Object.entries(majors).filter(([name]) => + name.toLowerCase().includes(lowerCaseSearchTerm) + ); + }, [majors, majorSearchTerm]); + + return ( + // Main container using Flexbox, full height, 3 columns +
+ + {/* Left Column (Majors List) */} +
+ Back to Form +

Select Major

+ setMajorSearchTerm(e.target.value)} + style={{ marginBottom: '0.5em', padding: '8px', border: '1px solid #ccc' }} + /> + {error &&
Error: {error}
} + {isLoadingMajors &&

Loading available majors...

} + {/* Scrollable Major List */} + {!isLoadingMajors && filteredMajors.length > 0 && ( +
+ {filteredMajors.map(([name, key]) => ( +
handleMajorSelect(key, name)} + style={{ + padding: '8px 12px', + cursor: 'pointer', + borderBottom: '1px solid #eee', + backgroundColor: selectedMajorKey === key ? '#e0e0e0' : 'transparent', + fontWeight: selectedMajorKey === key ? 'bold' : 'normal' + }} + className="major-list-item" + > + {name} + {selectedMajorKey === key && isLoadingPdf && (Loading...)} +
+ ))} +
+ )} + {/* ... other messages for no majors found ... */} + {!isLoadingMajors && filteredMajors.length === 0 && Object.keys(majors).length > 0 && ( +

No majors match your search.

+ )} + {!isLoadingMajors && Object.keys(majors).length === 0 && !error && ( +

No majors found.

+ )} +
+ + {/* Middle Column (Chat Interface) - Conditionally Rendered */} +
+ {selectedPdfFilename ? ( + + ) : ( +
+ Select a major to enable chat. +
+ )} +
+ + {/* Right Column (PDF Viewer) */} +
+ {/* Pass image filenames and loading/error state */} + +
+ +
+ ); +} + +export default AgreementViewerPage; \ No newline at end of file diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx new file mode 100644 index 0000000..e5c6001 --- /dev/null +++ b/src/components/ChatInterface.jsx @@ -0,0 +1,99 @@ +import React, { useState, useEffect } from 'react'; +import { fetchData } from '../services/api'; + +function ChatInterface({ imageFilenames, selectedMajorName }) { + const [messages, setMessages] = useState([]); + const [userInput, setUserInput] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [chatError, setChatError] = useState(null); + + // Clear chat when imageFilenames change (new agreement selected) + useEffect(() => { + setMessages([{ type: 'system', text: `Chatting about: ${selectedMajorName || 'Agreement'}` }]); + setUserInput(''); + setChatError(null); + }, [imageFilenames, selectedMajorName]); + + const handleSend = async () => { + if (!userInput.trim() || isLoading || !imageFilenames || imageFilenames.length === 0) return; + + const userMessage = { type: 'user', text: userInput }; + setMessages(prev => [...prev, userMessage]); + const currentInput = userInput; // Capture input before clearing + setUserInput(''); + setIsLoading(true); + setChatError(null); + + // --- Backend Call --- + try { + // *** Pass only 'chat' as the endpoint *** + const response = await fetchData('chat', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message: currentInput, + image_filenames: imageFilenames + }) + }); + + // Check if the response contains a reply + if (response && response.reply) { // Check if response is not null + setMessages(prev => [...prev, { type: 'bot', text: response.reply }]); + } else { + // If no reply, throw an error using the error message from the backend if available + // Check response object itself before accessing .error + throw new Error(response?.error || "No reply received or unexpected response format from chat API."); + } + } catch (err) { + console.error("Chat API error:", err); + // Display the error message to the user + setChatError(`Failed to get response: ${err.message}`); + // Optionally add a system message indicating the error + setMessages(prev => [...prev, { type: 'system', text: `Error: ${err.message}` }]); + } finally { + setIsLoading(false); + } + // --- End Backend Call --- + }; + + return ( +
+
+ {messages.map((msg, index) => ( +
+ + {msg.text} + +
+ ))} + {isLoading &&

Thinking...

} + {chatError &&

{chatError}

} +
+
+ setUserInput(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSend()} + placeholder="Ask about the agreement..." + style={{ flexGrow: 1, marginRight: '10px', padding: '8px' }} + disabled={isLoading || !imageFilenames || imageFilenames.length === 0} + /> + +
+
+ ); +} + +export default ChatInterface; diff --git a/src/components/CollegeTransferAI.jsx b/src/components/CollegeTransferAI.jsx deleted file mode 100644 index 00e721a..0000000 --- a/src/components/CollegeTransferAI.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import '../App.css'; // Ensure the CSS file is imported -import {hideDropdown,filterAcademicYears,filterInstitutions,filterNonCCs,filterMajors} from '../js/main'; // Import the utility functions -const CollegeTransferAI = () => { - return ( -
-

College Transfer AI

- - {/* Form to Get All Institutions for Sending Institution */} -
- - filterInstitutions()} - onFocus={() => filterInstitutions()} - onBlur={() => hideDropdown()} - /> -
-
- - {/* Form to Get All Institutions for Receiving Institution */} -
- - filterNonCCs()} - onFocus={() => filterNonCCs()} - onBlur={() => hideDropdown()} - disabled - /> -
-
-
- - filterAcademicYears()} - onFocus={() => filterAcademicYears()} - onBlur={() => hideDropdown()} - /> -
-
- - {/* Form to Get Majors */} -
- - filterMajors()} - onFocus={() => filterMajors()} - onBlur={() => hideDropdown()} - disabled - /> -
-
-
-

Result:

-
No data yet...
-
-
- ); -}; - - -export default CollegeTransferAI; \ No newline at end of file diff --git a/src/components/CollegeTransferForm.jsx b/src/components/CollegeTransferForm.jsx new file mode 100644 index 0000000..a918d1f --- /dev/null +++ b/src/components/CollegeTransferForm.jsx @@ -0,0 +1,295 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { fetchData } from '../services/api'; +import '../App.css'; + +const CollegeTransferForm = () => { + const navigate = useNavigate(); + + // --- State for fetched data --- + const [institutions, setInstitutions] = useState({}); + const [receivingInstitutions, setReceivingInstitutions] = useState({}); + const [academicYears, setAcademicYears] = useState({}); + // REMOVED: const [majors, setMajors] = useState({}); + + // --- State for input values and selections --- + const [sendingInputValue, setSendingInputValue] = useState(''); + const [receivingInputValue, setReceivingInputValue] = useState(''); + const [yearInputValue, setYearInputValue] = useState(''); + // REMOVED: const [majorInputValue, setMajorInputValue] = useState(''); + + const [selectedSendingId, setSelectedSendingId] = useState(null); + const [selectedReceivingId, setSelectedReceivingId] = useState(null); + const [selectedYearId, setSelectedYearId] = useState(null); + // REMOVED: const [selectedMajorKey, setSelectedMajorKey] = useState(null); + + // --- State for dropdown visibility and filtered options --- + const [showSendingDropdown, setShowSendingDropdown] = useState(false); + const [showReceivingDropdown, setShowReceivingDropdown] = useState(false); + const [showYearDropdown, setShowYearDropdown] = useState(false); + // REMOVED: const [showMajorDropdown, setShowMajorDropdown] = useState(false); + + const [filteredInstitutions, setFilteredInstitutions] = useState([]); + const [filteredReceiving, setFilteredReceiving] = useState([]); + const [filteredYears, setFilteredYears] = useState([]); + // REMOVED: const [filteredMajors, setFilteredMajors] = useState([]); + + // --- State for loading and results --- + const [isLoading] = useState(false); // Keep for initial loads if needed + const [resultMessage, setResultMessage] = useState('Select institutions and year to view available majors.'); // Updated message + const [error, setError] = useState(null); + + // --- Helper Functions --- + useCallback(() => { + setSendingInputValue(''); + setReceivingInputValue(''); + setYearInputValue(''); + // REMOVED: setMajorInputValue(''); + setSelectedSendingId(null); + setSelectedReceivingId(null); + setSelectedYearId(null); + // REMOVED: setSelectedMajorKey(null); + setReceivingInstitutions({}); + // REMOVED: setMajors({}); + setFilteredInstitutions([]); + setFilteredReceiving([]); + setFilteredYears([]); + // REMOVED: setFilteredMajors([]); + setShowSendingDropdown(false); + setShowReceivingDropdown(false); + setShowYearDropdown(false); + // REMOVED: setShowMajorDropdown(false); + setResultMessage('Select institutions and year to view available majors.'); // Updated message + setError(null); + }, []); + + // --- Effects for Initial Data Loading --- + useEffect(() => { + fetchData('institutions') + .then(data => setInstitutions(data)) + .catch(err => setError(`Failed to load institutions: ${err.message}`)); + fetchData('academic-years') + .then(data => setAcademicYears(data)) + .catch(err => setError(`Failed to load academic years: ${err.message}`)); + }, []); + + // --- Effects for Dependent Data Loading --- + useEffect(() => { + setReceivingInputValue(''); + setSelectedReceivingId(null); + setReceivingInstitutions({}); + setFilteredReceiving([]); + // Clear major related states if they were previously set (good practice after refactor) + // REMOVED: setMajorInputValue(''); + // REMOVED: setSelectedMajorKey(null); + // REMOVED: setMajors({}); + // REMOVED: setFilteredMajors([]); + + if (selectedSendingId) { + fetchData(`receiving-institutions?sendingInstitutionId=${selectedSendingId}`) + .then(data => setReceivingInstitutions(data)) + .catch(err => setError(`Failed to load receiving institutions: ${err.message}`)); + } + }, [selectedSendingId]); + + // REMOVED: useEffect hook that fetched majors + + // --- Effects for Filtering Dropdowns --- + const filter = useCallback( + ((value, data, setFiltered, setShowDropdown) => { + const lowerCaseValue = value.toLowerCase(); + const filtered = Object.entries(data) + .filter(([name]) => name.toLowerCase().includes(lowerCaseValue)) + .map(([name, id]) => ({ name, id })); + setFiltered(filtered); + setShowDropdown(true); + }), + [] + ); + + useEffect(() => { + if (sendingInputValue) { + filter(sendingInputValue, institutions, setFilteredInstitutions, setShowSendingDropdown); + } else { + // Keep dropdown open on focus, hide on blur or empty + if (!document.activeElement || document.activeElement.id !== 'searchInstitution') { + setShowSendingDropdown(false); + } + setFilteredInstitutions(Object.entries(institutions).map(([name, id]) => ({ name, id }))); // Show all on empty/focus + } + }, [sendingInputValue, institutions, filter]); + + useEffect(() => { + if (receivingInputValue && selectedSendingId) { + filter(receivingInputValue, receivingInstitutions, setFilteredReceiving, setShowReceivingDropdown); + } else { + if (!document.activeElement || document.activeElement.id !== 'receivingInstitution') { + setShowReceivingDropdown(false); + } + setFilteredReceiving(Object.entries(receivingInstitutions).map(([name, id]) => ({ name, id }))); // Show all on empty/focus + } + }, [receivingInputValue, receivingInstitutions, selectedSendingId, filter]); + + useEffect(() => { + if (yearInputValue) { + filter(yearInputValue, academicYears, setFilteredYears, setShowYearDropdown); + } else { + if (!document.activeElement || document.activeElement.id !== 'academicYears') { + setShowYearDropdown(false); + } + setFilteredYears(Object.entries(academicYears).map(([name, id]) => ({ name, id })).reverse()); // Show all on empty/focus + } + }, [yearInputValue, academicYears, filter]); + + // REMOVED: useEffect hook for filtering majors + + // --- Event Handlers --- + const handleInputChange = (e, setInputValue) => { + setInputValue(e.target.value); + setError(null); + }; + + const handleDropdownSelect = (item, inputId) => { + setError(null); + switch (inputId) { + case 'sending': + setSendingInputValue(item.name); + setSelectedSendingId(item.id); + setShowSendingDropdown(false); + setFilteredInstitutions([]); // Clear filter on select + break; + case 'receiving': + setReceivingInputValue(item.name); + setSelectedReceivingId(item.id); + setShowReceivingDropdown(false); + setFilteredReceiving([]); // Clear filter on select + break; + case 'year': + setYearInputValue(item.name); + setSelectedYearId(item.id); + setShowYearDropdown(false); + setFilteredYears([]); // Clear filter on select + break; + // REMOVED: case 'major' + default: + break; + } + }; + + // MODIFIED: Renamed and changed logic + const handleViewMajors = () => { // Keep name or rename to handleViewAgreements + if (!selectedSendingId || !selectedReceivingId || !selectedYearId) { + setError("Please select sending institution, receiving institution, and academic year first."); + return; + } + setError(null); + // Navigate to the new combined agreement viewer page + navigate(`/agreement/${selectedSendingId}/${selectedReceivingId}/${selectedYearId}`); + }; + + // --- Render Dropdown --- + const renderDropdown = (items, show, inputId) => { + // Ensure items is an array before mapping + if (!show || !Array.isArray(items) || items.length === 0) return null; + return ( +
+ {items.map((item) => ( +
handleDropdownSelect(item, inputId)} + > + {item.name} +
+ ))} +
+ ); + }; + + // --- Component JSX --- + return ( +
+

College Transfer AI

+ {error &&
Error: {error}
} + + {/* Sending Institution */} +
+ + handleInputChange(e, setSendingInputValue)} + onFocus={() => { + const allOptions = Object.entries(institutions).map(([name, id]) => ({ name, id })); + setFilteredInstitutions(allOptions); + setShowSendingDropdown(true); + }} + onBlur={() => setShowSendingDropdown(false)} // Delay to allow click + autoComplete="off" + /> + {renderDropdown(filteredInstitutions, showSendingDropdown, 'sending')} +
+ + {/* Receiving Institution */} +
+ + handleInputChange(e, setReceivingInputValue)} + onFocus={() => { + const allOptions = Object.entries(receivingInstitutions).map(([name, id]) => ({ name, id })); + setFilteredReceiving(allOptions); + setShowReceivingDropdown(true); + }} + onBlur={() => setShowReceivingDropdown(false)} + disabled={!selectedSendingId} + autoComplete="off" + /> + {renderDropdown(filteredReceiving, showReceivingDropdown, 'receiving')} +
+ + {/* Academic Year */} +
+ + handleInputChange(e, setYearInputValue)} + onFocus={() => { + const allOptions = Object.entries(academicYears) + .map(([name, id]) => ({ name, id })) + .reverse(); + setFilteredYears(allOptions); + setShowYearDropdown(true); + }} + onBlur={() => setShowYearDropdown(false)} + autoComplete="off" + /> + {renderDropdown(filteredYears, showYearDropdown, 'year')} +
+ + {/* MODIFIED: Button */} + + + {/* Result message area (optional, could be removed or kept for general status) */} +
+

Status:

+
{resultMessage}
+
+
+ ); +}; + +export default CollegeTransferForm; \ No newline at end of file diff --git a/src/components/PdfViewer.jsx b/src/components/PdfViewer.jsx new file mode 100644 index 0000000..4c467ec --- /dev/null +++ b/src/components/PdfViewer.jsx @@ -0,0 +1,47 @@ +import React from 'react'; + +// Accept imageFilenames directly as a prop +function PdfViewer({ imageFilenames, isLoading, error, filename }) { // Added isLoading, error, filename for context messages + + // Render content based on props + return ( +
+ {/* Show messages passed from parent */} + {!filename &&

Select a major to view the agreement.

} + {isLoading &&

Loading agreement images...

} + {error &&

Error loading agreement: {error}

} + + {!isLoading && !error && filename && (!imageFilenames || imageFilenames.length === 0) && ( +

No images found or extracted for this agreement.

+ )} + + {/* --- Scrollable Image Container --- */} + {!isLoading && !error && filename && imageFilenames && imageFilenames.length > 0 && ( +
+ {imageFilenames.map((imgFilename) => { + const imageUrl = `/api/image/${imgFilename}`; + return ( +
+ {`Page +
+ ); + })} +
+ )} + {/* --- End Scrollable Image Container --- */} +
+ ); +} + +export default PdfViewer; \ No newline at end of file diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 08a3ac9..0000000 --- a/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/js/main.js b/src/js/main.js deleted file mode 100644 index 51c8b61..0000000 --- a/src/js/main.js +++ /dev/null @@ -1,313 +0,0 @@ -const backendUrl = "http://localhost:5000"; - -let institutionsDict = {}; -let receivingInstitutions = {}; -let academicYears = {}; -let majors = {}; -let agreementGenerated = false; - -export async function populateData(endpoint, targetObj) { - try { - const response = await fetch(`${backendUrl}/${endpoint}`); - const data = await response.json(); - - Object.assign(targetObj, data); - } catch (error) { - console.error(`Error fetching ${endpoint}:`, error); - alert(`Failed to fetch ${endpoint}. Please try again.`); - } -} - -export function hideDropdown() { - setTimeout(() => { - const instDropdown = document.getElementById("institutionDropdown"); - if (instDropdown) instDropdown.innerHTML = ""; - const recDropdown = document.getElementById("receivingInstitutionDropdown"); - if (recDropdown) recDropdown.innerHTML = ""; - const yearsDropdown = document.getElementById("academicYearsDropdown"); - if (yearsDropdown) yearsDropdown.innerHTML = ""; - }, 150); -} - -export function updateMajorsInputState() { - const sendingInput = document.getElementById("searchInstitution"); - const receivingInput = document.getElementById("receivingInstitution"); - const academicYearInput = document.getElementById("academicYears"); - const majorsInput = document.getElementById("majors"); - - if ( - sendingInput && sendingInput.getAttribute("data-sending-institution-id") && - receivingInput && receivingInput.getAttribute("data-receiving-institution-id") && - academicYearInput && academicYearInput.getAttribute("data-academic-year-id") - ) { - majorsInput && (majorsInput.disabled = false); - populateData( - `majors?sendingInstitutionId=${sendingInput.getAttribute("data-sending-institution-id")}&receivingInstitutionId=${receivingInput.getAttribute("data-receiving-institution-id")}&academicYearId=${academicYearInput.getAttribute("data-academic-year-id")}&categoryCode=major`, majors - ); - majorsInput.setAttribute("data-major-key", majors[majorsInput.value]); - } else { - majorsInput && (majorsInput.disabled = true); - } -} - -export function filterDropdown(inputId, dropdownId, dataObj, dataAttr) { - const searchInput = document.getElementById(inputId).value.toLowerCase(); - const dropdown = document.getElementById(dropdownId); - if (!dropdown) return; - dropdown.innerHTML = ""; - - Object.entries(dataObj).reverse().forEach(([name, id]) => { - if (name.toLowerCase().includes(searchInput)) { - const option = document.createElement("div"); - option.textContent = name; - option.className = "dropdown-item"; - option.onmousedown = function () { - if (agreementGenerated) { - resetAllFields(); - return; // Optionally, prevent further actions until user re-selects - } - const input = document.getElementById(inputId); - input.value = name; - input.setAttribute(dataAttr, id.toString()); - dropdown.innerHTML = ""; - - // If this is the sending institution, enable receiving institution - if (inputId === "searchInstitution") { - const receivingInput = document.getElementById("receivingInstitution"); - if (receivingInput) receivingInput.disabled = false; - filterInstitutions(); - } - - // Reset majors input if any dependency changes - if ( - inputId === "searchInstitution" || - inputId === "receivingInstitution" || - inputId === "academicYears" - ) { - const majorsInput = document.getElementById("majors"); - if (majorsInput) { - majorsInput.value = ""; - majorsInput.removeAttribute("data-major-key"); - } - } - - if (typeof updateMajorsInputState === "function") { - updateMajorsInputState(); - } - - if (typeof getArticulationAgreement === "function") { - // Only call if majors input is filled and has a data-major-key - const majorsInput = document.getElementById("majors"); - if ( - majorsInput && - majorsInput.value && - majorsInput.getAttribute("data-major-key") - ) { - getArticulationAgreement(inputId); // Pass the field that changed - } - } - }; - dropdown.appendChild(option); - } - }); -} - -export function filterInstitutions() { - filterDropdown("searchInstitution", "institutionDropdown", institutionsDict, "data-sending-institution-id"); - const sendingInput = document.getElementById("searchInstitution"); - const receivingInput = document.getElementById("receivingInstitution"); - if (sendingInput && receivingInput) { - if (sendingInput.getAttribute("data-sending-institution-id")) { - populateData( - `receiving-institutions?sendingInstitutionId=${sendingInput.getAttribute("data-sending-institution-id")}`, - receivingInstitutions - ); - receivingInput.disabled = false; - } else { - receivingInput.disabled = true; - } - } - updateMajorsInputState(); -} - -export function filterReceivingInstitutions() { - filterDropdown("receivingInstitution", "receivingInstitutionDropdown", receivingInstitutions, "data-receiving-institution-id"); - - -export function filterAcademicYears() { - filterDropdown("academicYears", "academicYearsDropdown", academicYears, "data-academic-year-id"); - updateMajorsInputState(); -} - -export function filterMajors() { - filterDropdown("majors", "majorsDropdown", majors, "data-major-id"); -} - -export async function getInstitutions() { - try { - const response = await fetch(`${backendUrl}/institutions`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } -} - -export async function getAcademicYears() { - try { - const response = await fetch(`${backendUrl}/academic-years`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } -} - -export async function getAllMajors() { - const sendingInstitutionInput = document.getElementById("sendingInstitutionId"); - const receivingInstitutionInput = document.getElementById("receivingInstitutionId"); - const academicYearInput = document.getElementById("academicYearId"); - const categoryCodeInput = document.getElementById("categoryCode"); - - if (!sendingInstitutionInput || !receivingInstitutionInput || !academicYearInput || !categoryCodeInput) { - alert("Please fill in all fields."); - return; - } - - const sendingInstitutionId = sendingInstitutionInput.value; - const receivingInstitutionId = receivingInstitutionInput.value; - const academicYearId = academicYearInput.value; - const categoryCode = categoryCodeInput.value; - - if (!sendingInstitutionId || !receivingInstitutionId || !academicYearId || !categoryCode) { - alert("Please fill in all fields."); - return; - } - - try { - const response = await fetch(`${backendUrl}/majors?sendingInstitutionId=${sendingInstitutionId}&receivingInstitutionId=${receivingInstitutionId}&academicYearId=${academicYearId}&categoryCode=${categoryCode}`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } -} - -export async function getMajorKey() { - const sendingInstitutionInput = document.getElementById("sendingInstitutionId"); - const receivingInstitutionInput = document.getElementById("receivingInstitutionId"); - const academicYearInput = document.getElementById("academicYearId"); - const categoryCodeInput = document.getElementById("categoryCode"); - const majorInput = document.getElementById("major"); - - if (!sendingInstitutionInput || !receivingInstitutionInput || !academicYearInput || !categoryCodeInput || !majorInput) { - alert("Please fill in all fields."); - return; - } - - const sendingInstitutionId = sendingInstitutionInput.value; - const receivingInstitutionId = receivingInstitutionInput.value; - const academicYearId = academicYearInput.value; - const categoryCode = categoryCodeInput.value; - const major = majorInput.value; - - if (!sendingInstitutionId || !receivingInstitutionId || !academicYearId || !categoryCode || !major) { - alert("Please fill in all fields."); - return; - } - - try { - const response = await fetch(`${backendUrl}/major-key?sendingInstitutionId=${sendingInstitutionId}&receivingInstitutionId=${receivingInstitutionId}&academicYearId=${academicYearId}&categoryCode=${categoryCode}&major=${major}`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } -} - -export function allDependenciesSelected() { - const sendingInput = document.getElementById("searchInstitution"); - const receivingInput = document.getElementById("receivingInstitution"); - const academicYearInput = document.getElementById("academicYears"); - return ( - sendingInput && sendingInput.getAttribute("data-sending-institution-id") && - receivingInput && receivingInput.getAttribute("data-receiving-institution-id") && - academicYearInput && academicYearInput.getAttribute("data-academic-year-id") - ); -} - -export async function getArticulationAgreement(changedField) { - const majorsInput = document.getElementById("majors"); - const key = majorsInput.getAttribute("data-major-key"); - if (!key) { - alert("No Agreement Found"); - return; - } - - showLoadingAgreement(); - - try { - const response = await fetch(`${backendUrl}/articulation-agreement?key=${key}`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } - - // If a non-major field was changed and all dependencies are selected, re-populate majors - if (changedField !== "majors" && allDependenciesSelected()) { - const sendingInput = document.getElementById("searchInstitution"); - const receivingInput = document.getElementById("receivingInstitution"); - const academicYearInput = document.getElementById("academicYears"); - await populateData( - `majors?sendingInstitutionId=${sendingInput.getAttribute("data-sending-institution-id")}&receivingInstitutionId=${receivingInput.getAttribute("data-receiving-institution-id")}&academicYearId=${academicYearInput.getAttribute("data-academic-year-id")}&categoryCode=major`, - majors - ); - // Update data-major-key after repopulating majors - if (majorsInput) { - const newMajorKey = majors[majorsInput.value]; - if (newMajorKey) { - majorsInput.setAttribute("data-major-key", newMajorKey); - } else { - majorsInput.removeAttribute("data-major-key"); - } - } - } -} - -export function showLoadingAgreement() { - const resultContent = document.getElementById("resultContent"); - resultContent.textContent = "Loading Agreement..."; -} - -window.onload = function () { - populateData('institutions', institutionsDict); - populateData('academic-years', academicYears); -}; - - -export function displayResult(data) { - const resultContent = document.getElementById("resultContent"); - if (data.pdf_filename) { - window.open(`${backendUrl}/pdf/${data.pdf_filename}`, '_blank'); - resultContent.textContent = "PDF opened in a new tab."; - agreementGenerated = true; // Set flag - resetAllFields(); // Reset fields right after generating agreement - } else { - resultContent.textContent = JSON.stringify(data, null, 4); - } -} - -export function resetAllFields() { - document.getElementById("searchInstitution").value = ""; - document.getElementById("searchInstitution").removeAttribute("data-sending-institution-id"); - document.getElementById("receivingInstitution").value = ""; - document.getElementById("receivingInstitution").removeAttribute("data-receiving-institution-id"); - document.getElementById("receivingInstitution").disabled = true; - document.getElementById("academicYears").value = ""; - document.getElementById("academicYears").removeAttribute("data-academic-year-id"); - document.getElementById("majors").value = ""; - document.getElementById("majors").removeAttribute("data-major-key"); - agreementGenerated = false; - updateMajorsInputState(); -} diff --git a/src/main.jsx b/src/main.jsx index b9a1a6d..c06e259 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,10 +1,9 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.jsx' +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; // Import BrowserRouter +import App from './App.jsx'; createRoot(document.getElementById('root')).render( - + {/* Wrap App with BrowserRouter */} - , -) + +); diff --git a/src/services/api.js b/src/services/api.js new file mode 100644 index 0000000..fbc7435 --- /dev/null +++ b/src/services/api.js @@ -0,0 +1,65 @@ + +/** + * Fetches data from a backend endpoint via the /api proxy. + * @param {string} endpoint - The API endpoint *without* the leading /api/ (e.g., 'institutions', 'chat', 'pdf-images/filename.pdf'). + * @param {object} options - Optional fetch options (method, headers, body, etc.). Defaults to GET. + * @returns {Promise} - A promise that resolves with the JSON data or null for empty responses. + * @throws {Error} - Throws an error if the fetch fails or response is not ok. + */ +export async function fetchData(endpoint, options = {}) { + // Construct the full URL, always prepending /api/ + // Ensure no double slashes if endpoint accidentally starts with one + const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; + const url = `/api/${cleanEndpoint}`; // Use relative path for the proxy + + try { + console.log(`Fetching data from: ${url} with options:`, options); // Log URL and options + + // *** Pass the options object as the second argument to fetch *** + const response = await fetch(url, options); + + if (!response.ok) { + // Try to get error details from response body if available + let errorBody = null; + try { + // Use .text() first in case the error isn't JSON + const text = await response.text(); + if (text) { + errorBody = JSON.parse(text); // Try parsing as JSON + } + } catch (e) { + // Ignore if response body is not JSON or empty + console.warn("Could not parse error response body as JSON:", e); + } + // Use error from body if available, otherwise use status text + const errorMessage = errorBody?.error || response.statusText || `HTTP error! status: ${response.status}`; + throw new Error(errorMessage); + } + + // Handle cases where response might be empty (e.g., 204 No Content) + if (response.status === 204) { + return null; // Return null for empty successful responses + } + + // Check content type before assuming JSON + const contentType = response.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1) { + const data = await response.json(); + return data; + } else { + // Handle non-JSON responses if necessary, or throw an error + console.warn(`Received non-JSON response from ${url}`); + return await response.text(); // Or handle differently + } + + } catch (error) { + console.error(`Error fetching ${url}:`, error); + // Re-throw the error so the component can handle it + // Ensure it's an actual Error object + if (error instanceof Error) { + throw error; + } else { + throw new Error(String(error)); + } + } +} diff --git a/static/css/styles.css b/static/css/styles.css deleted file mode 100644 index af6d85e..0000000 --- a/static/css/styles.css +++ /dev/null @@ -1,50 +0,0 @@ -body { - font-family: Arial, sans-serif; - margin: 20px; -} -.form-group { - margin-bottom: 15px; -} -label { - display: block; - margin-bottom: 5px; -} -input, select, button { - padding: 10px; - width: 100%; - max-width: 400px; - margin-bottom: 10px; -} -button { - background-color: #007BFF; - color: white; - border: none; - cursor: pointer; -} -button:hover { - background-color: #0056b3; -} -.result { - margin-top: 20px; - padding: 10px; - border: 1px solid #ccc; - background-color: #f9f9f9; -} -.dropdown { - position: absolute; - background-color: white; - border: 1px solid #ccc; - max-height: 200px; - overflow-y: auto; - width: 100%; - z-index: 1000; -} - -.dropdown-item { - padding: 10px; - cursor: pointer; -} - -.dropdown-item:hover { - background-color: #f1f1f1; -} \ No newline at end of file diff --git a/tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc b/tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc deleted file mode 100644 index e79f0bf8510d634c22bdc8cfb3c75f3a63076128..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9615 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 cITm|FVgX>2(IREJN5qhiDrh+h zCQ`$^JTk!aNK3O~=QLldXv?YRwUJFfR*5aApVwRQa?ihs05Q4`A~A%>?t>T^LhNVb z#fapPjsMC^>VNT~|Lb1%%SU?1i?PqS^bvmC%Fs)d_&WfimE%2V^?QU?4n*sga}l*S z&mn8HGIB<<#y(j(6Dvc#s%K!$D_zOpIEO0zim|dQQ;}|YI->UGITY2WimZBQ$o9-0 zE~3D8zh0wVVDRw5i2AEO)~u{GUW}bgBIGA6!#Yq&mb8#dtsIX04A?so$@XT5qF+bR z84zd^7qs9q+`^-K$cTz|88!}R9_8NMHc<^7MQ@^LFIBjU>(FFokqMs~x)7g5>sn#r z#w}uay)rAPoFD!V}!CswmalTwCl@?mLJSUhr^{YM?u4oFE`mOO|mC>L| zH~f>XAdS|+JR=TqKXd)s5G;=!TqORx01XwKG`q@UMoU`G1o6>t=wzJgUz=JT&Wd#y1?aV*N831T~Ub1H~`dex;2hD zCrpIEG5|qXE=(}hAEoZp8Ev@DhD>_6={t3)gEKq8H6jx=DySTBJlDA+CpNGv>~+d) zPCViW%4Ej%eZoS2GV?*ODF_!fW^%%YlzBEI0dr_0z>LVW&G9->5;+D)d#&ku!u2_L zRr%#|+U9lI^s3=1yXMg~?AbnU5IKG^j2`lQnKAG>se))=VUxSi{C%uWKm|!TDd5a>yuNgPwE{-@s!xTWtCtAB zBWTc{Z!&rtvcgxFsOOOyxy7*0VaFG*uBx=~aJ0!OQVcew8D^>m=gY7lHm)&38gTIT zItweH%JSe_oe$rHYW*Djnx5{YXTHNbxyf(mAI@**@>{w5<6G@qzMVUDFSTP#Z5y*& z#_SX0=)J^Kee6N{e!88T@8~Cg{`@J4T_cFFsz`G8VV>-uEYc zv27H(Y-fP(13>tArO#~*aeeU0PS5<3p81=P!4Z7<(a9@X z{6{Tw#Yh#>QjbcP9R#I()$Z1Pv0GeFi$zA@g+xHj_eC<*lQGr*<>cN=OEG+j!GRmz z#_-sGK)V1!Y)(P-cN?_Y^vHYc1h7yGh$|D%;+m%YhR_%2XSA@JFtuqm58{6SXe{=| diff --git a/vite.config.js b/vite.config.js index d8d8c9a..da5afa4 100644 --- a/vite.config.js +++ b/vite.config.js @@ -5,6 +5,29 @@ import react from '@vitejs/plugin-react-swc' export default defineConfig({ plugins: [react()], server: { - port: 5173 + port: 5173, // Your frontend port + proxy: { + '/api': { + target: 'http://127.0.0.1:5000', // Use IPv4 address explicitly + changeOrigin: true, // Recommended + rewrite: (path) => { + console.log(`Vite proxy intercepted: ${path}`); + const rewritten = path.replace(/^\/api/, ''); + console.log(`Rewritten to: ${rewritten}`); + return rewritten; + }, + configure: (proxy) => { + proxy.on('error', (err) => { + console.log('Proxy error:', err); + }); + proxy.on('proxyReq', (proxyReq, req) => { + console.log(`Proxy request: ${req.method} ${req.url} → ${proxyReq.method} ${proxyReq.path}`); + }); + proxy.on('proxyRes', (proxyRes, req) => { + console.log(`Proxy response: ${req.method} ${req.url} → ${proxyRes.statusCode}`); + }); + } + } + } } })