diff --git a/.gitignore b/.gitignore index 6cb294929..942e3a4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ *.tmp *.swp *~ -.codegraph +*.codegraph +*/node_modules +*/dist diff --git a/web/env.d.ts b/web/env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/web/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/web/index.html b/web/index.html new file mode 100644 index 000000000..58d3af594 --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + + Codex Proxy RS + + +
+ + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 000000000..c0cac3f81 --- /dev/null +++ b/web/package.json @@ -0,0 +1,30 @@ +{ + "name": "codex-proxy-rs-web", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "pnpm@11.7.0", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@lucide/vue": "^1.18.0", + "axios": "^1.18.0", + "echarts": "^6.1.0", + "pinia": "^3.0.4", + "vue": "^3.5.38", + "vue-router": "^5.1.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.1", + "@types/node": "^25.9.3", + "@vitejs/plugin-vue": "^6.0.7", + "@vue/tsconfig": "^0.9.1", + "tailwindcss": "^4.3.1", + "typescript": "^6.0.3", + "vite": "^8.0.16", + "vue-tsc": "^3.3.5" + } +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 000000000..094c9eda7 --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,1691 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@lucide/vue': + specifier: ^1.18.0 + version: 1.18.0(vue@3.5.38(typescript@6.0.3)) + axios: + specifier: ^1.18.0 + version: 1.18.0 + echarts: + specifier: ^6.1.0 + version: 6.1.0 + pinia: + specifier: ^3.0.4 + version: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) + vue: + specifier: ^3.5.38 + version: 3.5.38(typescript@6.0.3) + vue-router: + specifier: ^5.1.0 + version: 5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) + devDependencies: + '@tailwindcss/vite': + specifier: ^4.3.1 + version: 4.3.1(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0)) + '@types/node': + specifier: ^25.9.3 + version: 25.9.3 + '@vitejs/plugin-vue': + specifier: ^6.0.7 + version: 6.0.7(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) + '@vue/tsconfig': + specifier: ^0.9.1 + version: 0.9.1(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) + tailwindcss: + specifier: ^4.3.1 + version: 4.3.1 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^8.0.16 + version: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0) + vue-tsc: + specifier: ^3.3.5 + version: 3.3.5(typescript@6.0.3) + +packages: + + '@babel/generator@8.0.0-rc.6': + resolution: {integrity: sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@8.0.0-rc.6': + resolution: {integrity: sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@8.0.0-rc.6': + resolution: {integrity: sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@8.0.0-rc.6': + resolution: {integrity: sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@babel/types@8.0.0-rc.6': + resolution: {integrity: sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lucide/vue@1.18.0': + resolution: {integrity: sha512-DmnUpDB85PlMZ+ofjZLcKq3JoJnaD1bk7SIj9xwUvqerfNqA6hCLa0/m3gIybH6rdrErABbqvTD8yYJdNqiZ3Q==} + peerDependencies: + vue: '>=3.0.1' + + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tailwindcss/node@4.3.1': + resolution: {integrity: sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==} + + '@tailwindcss/oxide-android-arm64@4.3.1': + resolution: {integrity: sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.1': + resolution: {integrity: sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.1': + resolution: {integrity: sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.1': + resolution: {integrity: sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1': + resolution: {integrity: sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.1': + resolution: {integrity: sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.1': + resolution: {integrity: sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.1': + resolution: {integrity: sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.1': + resolution: {integrity: sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.1': + resolution: {integrity: sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.1': + resolution: {integrity: sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.1': + resolution: {integrity: sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.1': + resolution: {integrity: sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.1': + resolution: {integrity: sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + + '@types/node@25.9.3': + resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + + '@vitejs/plugin-vue@6.0.7': + resolution: {integrity: sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vue-macros/common@3.1.2': + resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==} + engines: {node: '>=20.19.0'} + peerDependencies: + vue: ^2.7.0 || ^3.2.25 + peerDependenciesMeta: + vue: + optional: true + + '@vue/compiler-core@3.5.38': + resolution: {integrity: sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==} + + '@vue/compiler-dom@3.5.38': + resolution: {integrity: sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==} + + '@vue/compiler-sfc@3.5.38': + resolution: {integrity: sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==} + + '@vue/compiler-ssr@3.5.38': + resolution: {integrity: sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==} + + '@vue/devtools-api@7.7.9': + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + + '@vue/devtools-api@8.1.3': + resolution: {integrity: sha512-73NMCvxXh8Hyozc/jiwqTFWVcCMyi11U1zmrq4DoukQJnuo8JHt6FsNu4HdeUDa8SpIp5vb7Q22GWgIq0efsXg==} + + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + + '@vue/devtools-kit@8.1.3': + resolution: {integrity: sha512-cRn7GXiCQkMYU2Z3h3pM4YO/ndbx9FY1yLDAqIqPLcmIq4H6zAOJHein6tvZU3AfPwgrodqLiPBEF+YQaS8AxA==} + + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + + '@vue/devtools-shared@8.1.3': + resolution: {integrity: sha512-CM3uIPL+v+lrJUk33+pxspYo0MhuMWlCvf7zC9fybifvCPyM2jUbYRPwoYEJgYbwRqPikm5HozbUhp60MF2QuA==} + + '@vue/language-core@3.3.5': + resolution: {integrity: sha512-UkKu5nhX89fg4VhlG/FOeI10G3cj/7radKT/cy9BT4Q9qJmJlSTAc/dP63Xqs29aypN4f39xUV6PsLNk/dcD6g==} + + '@vue/reactivity@3.5.38': + resolution: {integrity: sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==} + + '@vue/runtime-core@3.5.38': + resolution: {integrity: sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==} + + '@vue/runtime-dom@3.5.38': + resolution: {integrity: sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==} + + '@vue/server-renderer@3.5.38': + resolution: {integrity: sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==} + peerDependencies: + vue: 3.5.38 + + '@vue/shared@3.5.38': + resolution: {integrity: sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==} + + '@vue/tsconfig@0.9.1': + resolution: {integrity: sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==} + peerDependencies: + typescript: '>= 5.8' + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + + ast-kit@2.2.0: + resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} + engines: {node: '>=20.19.0'} + + ast-walker-scope@0.9.0: + resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==} + engines: {node: '>=20.19.0'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.18.0: + resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} + + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + echarts@6.1.0: + resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==} + + enhanced-resolve@5.21.6: + resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} + engines: {node: '>=10.13.0'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + local-pkg@1.2.1: + resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} + engines: {node: '>=14'} + + magic-string-ast@1.0.3: + resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==} + engines: {node: '>=20.19.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pinia@3.0.4: + resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} + peerDependencies: + typescript: '>=4.5.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + + tailwindcss@4.3.1: + resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} + engines: {node: '>=20.19.0'} + + unplugin@3.0.0: + resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} + engines: {node: ^20.19.0 || >=22.12.0} + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-router@5.1.0: + resolution: {integrity: sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==} + peerDependencies: + '@pinia/colada': '>=0.21.2' + '@vue/compiler-sfc': ^3.5.34 + pinia: ^3.0.4 + vite: ^7.0.0 || ^8.0.0 + vue: ^3.5.34 + peerDependenciesMeta: + '@pinia/colada': + optional: true + '@vue/compiler-sfc': + optional: true + pinia: + optional: true + vite: + optional: true + + vue-tsc@3.3.5: + resolution: {integrity: sha512-Rzh/G2MmNlMSAMTiQEjDrsb4dgB/jbtEM47rVN2NtidF1dfb/q4w4QvpQBtW5+y3y5H27Hjh7deVwk+YB02fNg==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.38: + resolution: {integrity: sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + zrender@6.1.0: + resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==} + +snapshots: + + '@babel/generator@8.0.0-rc.6': + dependencies: + '@babel/parser': 8.0.0-rc.6 + '@babel/types': 8.0.0-rc.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-string-parser@8.0.0-rc.6': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-identifier@8.0.0-rc.6': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/parser@8.0.0-rc.6': + dependencies: + '@babel/types': 8.0.0-rc.6 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@babel/types@8.0.0-rc.6': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.6 + '@babel/helper-validator-identifier': 8.0.0-rc.6 + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lucide/vue@1.18.0(vue@3.5.38(typescript@6.0.3))': + dependencies: + vue: 3.5.38(typescript@6.0.3) + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.133.0': {} + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tailwindcss/node@4.3.1': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.21.6 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.1 + + '@tailwindcss/oxide-android-arm64@4.3.1': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.1': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.1': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.1': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.1': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.1': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.1': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.1': + optional: true + + '@tailwindcss/oxide@4.3.1': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.1 + '@tailwindcss/oxide-darwin-arm64': 4.3.1 + '@tailwindcss/oxide-darwin-x64': 4.3.1 + '@tailwindcss/oxide-freebsd-x64': 4.3.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.1 + '@tailwindcss/oxide-linux-x64-musl': 4.3.1 + '@tailwindcss/oxide-wasm32-wasi': 4.3.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.1 + + '@tailwindcss/vite@4.3.1(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0))': + dependencies: + '@tailwindcss/node': 4.3.1 + '@tailwindcss/oxide': 4.3.1 + tailwindcss: 4.3.1 + vite: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0) + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/jsesc@2.5.1': {} + + '@types/node@25.9.3': + dependencies: + undici-types: 7.24.6 + + '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0) + vue: 3.5.38(typescript@6.0.3) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue-macros/common@3.1.2(vue@3.5.38(typescript@6.0.3))': + dependencies: + '@vue/compiler-sfc': 3.5.38 + ast-kit: 2.2.0 + local-pkg: 1.2.1 + magic-string-ast: 1.0.3 + unplugin-utils: 0.3.1 + optionalDependencies: + vue: 3.5.38(typescript@6.0.3) + + '@vue/compiler-core@3.5.38': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.38 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.38': + dependencies: + '@vue/compiler-core': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/compiler-sfc@3.5.38': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.38 + '@vue/compiler-dom': 3.5.38 + '@vue/compiler-ssr': 3.5.38 + '@vue/shared': 3.5.38 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.15 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.38': + dependencies: + '@vue/compiler-dom': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + + '@vue/devtools-api@8.1.3': + dependencies: + '@vue/devtools-kit': 8.1.3 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-kit@8.1.3': + dependencies: + '@vue/devtools-shared': 8.1.3 + birpc: 2.9.0 + hookable: 5.5.3 + perfect-debounce: 2.1.0 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + + '@vue/devtools-shared@8.1.3': {} + + '@vue/language-core@3.3.5': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.38 + '@vue/shared': 3.5.38 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.4 + + '@vue/reactivity@3.5.38': + dependencies: + '@vue/shared': 3.5.38 + + '@vue/runtime-core@3.5.38': + dependencies: + '@vue/reactivity': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/runtime-dom@3.5.38': + dependencies: + '@vue/reactivity': 3.5.38 + '@vue/runtime-core': 3.5.38 + '@vue/shared': 3.5.38 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.38(vue@3.5.38(typescript@6.0.3))': + dependencies: + '@vue/compiler-ssr': 3.5.38 + '@vue/shared': 3.5.38 + vue: 3.5.38(typescript@6.0.3) + + '@vue/shared@3.5.38': {} + + '@vue/tsconfig@0.9.1(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3))': + optionalDependencies: + typescript: 6.0.3 + vue: 3.5.38(typescript@6.0.3) + + acorn@8.17.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + alien-signals@3.2.1: {} + + ast-kit@2.2.0: + dependencies: + '@babel/parser': 7.29.7 + pathe: 2.0.3 + + ast-walker-scope@0.9.0: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + ast-kit: 2.2.0 + + asynckit@0.4.0: {} + + axios@1.18.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + birpc@2.9.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + detect-libc@2.1.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + echarts@6.1.0: + dependencies: + tslib: 2.3.0 + zrender: 6.1.0 + + enhanced-resolve@5.21.6: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + entities@7.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + estree-walker@2.0.2: {} + + exsolve@1.0.8: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + follow-redirects@1.16.0: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hookable@5.5.3: {} + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + is-what@5.5.0: {} + + jiti@2.7.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + local-pkg@1.2.1: + dependencies: + mlly: 1.8.2 + pkg-types: 2.3.1 + quansync: 0.2.11 + + magic-string-ast@1.0.3: + dependencies: + magic-string: 0.30.21 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mitt@3.0.1: {} + + mlly@1.8.2: + dependencies: + acorn: 8.17.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.12: {} + + path-browserify@1.0.1: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + perfect-debounce@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)): + dependencies: + '@vue/devtools-api': 7.7.9 + vue: 3.5.38(typescript@6.0.3) + optionalDependencies: + typescript: 6.0.3 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + pkg-types@2.3.1: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-from-env@2.1.0: {} + + quansync@0.2.11: {} + + readdirp@5.0.0: {} + + rfdc@1.4.1: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + scule@1.3.0: {} + + source-map-js@1.2.1: {} + + speakingurl@14.0.1: {} + + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + + tailwindcss@4.3.1: {} + + tapable@2.3.3: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tslib@2.3.0: {} + + tslib@2.8.1: + optional: true + + typescript@6.0.3: {} + + ufo@1.6.4: {} + + undici-types@7.24.6: {} + + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.4 + + unplugin@3.0.0: + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + + vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 25.9.3 + fsevents: 2.3.3 + jiti: 2.7.0 + yaml: 2.9.0 + + vscode-uri@3.1.0: {} + + vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)): + dependencies: + '@babel/generator': 8.0.0-rc.6 + '@vue-macros/common': 3.1.2(vue@3.5.38(typescript@6.0.3)) + '@vue/devtools-api': 8.1.3 + ast-walker-scope: 0.9.0 + chokidar: 5.0.0 + json5: 2.2.3 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 + muggle-string: 0.4.1 + pathe: 2.0.3 + picomatch: 4.0.4 + scule: 1.3.0 + tinyglobby: 0.2.17 + unplugin: 3.0.0 + unplugin-utils: 0.3.1 + vue: 3.5.38(typescript@6.0.3) + yaml: 2.9.0 + optionalDependencies: + '@vue/compiler-sfc': 3.5.38 + pinia: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) + vite: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(yaml@2.9.0) + + vue-tsc@3.3.5(typescript@6.0.3): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.3.5 + typescript: 6.0.3 + + vue@3.5.38(typescript@6.0.3): + dependencies: + '@vue/compiler-dom': 3.5.38 + '@vue/compiler-sfc': 3.5.38 + '@vue/runtime-dom': 3.5.38 + '@vue/server-renderer': 3.5.38(vue@3.5.38(typescript@6.0.3)) + '@vue/shared': 3.5.38 + optionalDependencies: + typescript: 6.0.3 + + webpack-virtual-modules@0.6.2: {} + + yaml@2.9.0: {} + + zrender@6.1.0: + dependencies: + tslib: 2.3.0 diff --git a/web/pnpm-workspace.yaml b/web/pnpm-workspace.yaml new file mode 100644 index 000000000..8e8049db6 --- /dev/null +++ b/web/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +minimumReleaseAgeExclude: + - axios@1.18.0 diff --git a/web/public/favicon.svg b/web/public/favicon.svg new file mode 100644 index 000000000..9a773b28f --- /dev/null +++ b/web/public/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 000000000..a78fb6fd3 --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/web/src/api/index.ts b/web/src/api/index.ts new file mode 100644 index 000000000..d13cb1509 --- /dev/null +++ b/web/src/api/index.ts @@ -0,0 +1,2 @@ +export * from './request' +export type * from './types' diff --git a/web/src/api/request.ts b/web/src/api/request.ts new file mode 100644 index 000000000..7dcee361b --- /dev/null +++ b/web/src/api/request.ts @@ -0,0 +1,70 @@ +import axios, { type AxiosHeaders, type AxiosRequestConfig, type AxiosResponse, type AxiosResponseHeaders } from 'axios' + +import type { ApiEnvelope } from './types' + +export class ApiError extends Error { + constructor( + message: string, + public readonly status: number, + public readonly requestId?: string, + ) { + super(message) + this.name = 'ApiError' + } +} + +export const http = axios.create({ + baseURL: '/', + withCredentials: true, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, +}) + +type ResponseHeaders = AxiosResponseHeaders | Partial + +function headerValue(headers: ResponseHeaders | undefined, name: string) { + if (!headers) return undefined + const value = headers[name] ?? headers[name.toLowerCase()] + return typeof value === 'string' ? value : undefined +} + +function isApiEnvelope(value: unknown): value is ApiEnvelope { + return typeof value === 'object' + && value !== null + && 'data' in value + && 'code' in value + && 'message' in value +} + +export async function requestJson( + url: string, + config: Omit, 'url'> = {}, +): Promise { + try { + const response = await http.request | T, AxiosResponse | T>, D>({ + url, + ...config, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...config.headers, + }, + }) + + return isApiEnvelope(response.data) ? response.data.data : response.data + } catch (error) { + if (axios.isAxiosError>(error)) { + const requestId = headerValue(error.response?.headers, 'x-request-id') + ?? error.response?.data?.requestId + throw new ApiError( + error.response?.data?.message ?? error.message, + error.response?.status ?? 0, + requestId, + ) + } + + throw error + } +} diff --git a/web/src/api/types.ts b/web/src/api/types.ts new file mode 100644 index 000000000..7a339a5ba --- /dev/null +++ b/web/src/api/types.ts @@ -0,0 +1,6 @@ +export interface ApiEnvelope { + code: string + message: string + requestId: string + data: T +} diff --git a/web/src/components/base/BaseBadge.vue b/web/src/components/base/BaseBadge.vue new file mode 100644 index 000000000..11d2fe8da --- /dev/null +++ b/web/src/components/base/BaseBadge.vue @@ -0,0 +1,34 @@ + + + diff --git a/web/src/components/base/BaseButton.vue b/web/src/components/base/BaseButton.vue new file mode 100644 index 000000000..0519553c5 --- /dev/null +++ b/web/src/components/base/BaseButton.vue @@ -0,0 +1,55 @@ + + + diff --git a/web/src/components/base/BaseCard.vue b/web/src/components/base/BaseCard.vue new file mode 100644 index 000000000..6106552e6 --- /dev/null +++ b/web/src/components/base/BaseCard.vue @@ -0,0 +1,33 @@ + + + diff --git a/web/src/components/base/BaseEmpty.vue b/web/src/components/base/BaseEmpty.vue new file mode 100644 index 000000000..722f841d1 --- /dev/null +++ b/web/src/components/base/BaseEmpty.vue @@ -0,0 +1,14 @@ + + + diff --git a/web/src/components/base/BaseIconButton.vue b/web/src/components/base/BaseIconButton.vue new file mode 100644 index 000000000..e84b98c59 --- /dev/null +++ b/web/src/components/base/BaseIconButton.vue @@ -0,0 +1,25 @@ + + + diff --git a/web/src/components/base/BaseInput.vue b/web/src/components/base/BaseInput.vue new file mode 100644 index 000000000..9a377ff27 --- /dev/null +++ b/web/src/components/base/BaseInput.vue @@ -0,0 +1,38 @@ + + + diff --git a/web/src/components/base/BaseModal.vue b/web/src/components/base/BaseModal.vue new file mode 100644 index 000000000..28642a1be --- /dev/null +++ b/web/src/components/base/BaseModal.vue @@ -0,0 +1,48 @@ + + + diff --git a/web/src/components/base/BaseSegmented.vue b/web/src/components/base/BaseSegmented.vue new file mode 100644 index 000000000..22cc1968c --- /dev/null +++ b/web/src/components/base/BaseSegmented.vue @@ -0,0 +1,29 @@ + + + diff --git a/web/src/components/base/BaseSelect.vue b/web/src/components/base/BaseSelect.vue new file mode 100644 index 000000000..7ce91fbf0 --- /dev/null +++ b/web/src/components/base/BaseSelect.vue @@ -0,0 +1,30 @@ + + + diff --git a/web/src/components/base/BaseSpinner.vue b/web/src/components/base/BaseSpinner.vue new file mode 100644 index 000000000..0382040bf --- /dev/null +++ b/web/src/components/base/BaseSpinner.vue @@ -0,0 +1,14 @@ + + + diff --git a/web/src/components/base/BaseTable.vue b/web/src/components/base/BaseTable.vue new file mode 100644 index 000000000..a99c5fda1 --- /dev/null +++ b/web/src/components/base/BaseTable.vue @@ -0,0 +1,63 @@ + + + diff --git a/web/src/components/base/BaseToast.vue b/web/src/components/base/BaseToast.vue new file mode 100644 index 000000000..71e785225 --- /dev/null +++ b/web/src/components/base/BaseToast.vue @@ -0,0 +1,47 @@ + + + diff --git a/web/src/components/charts/BaseChart.vue b/web/src/components/charts/BaseChart.vue new file mode 100644 index 000000000..b5222c43a --- /dev/null +++ b/web/src/components/charts/BaseChart.vue @@ -0,0 +1,33 @@ + + + diff --git a/web/src/composables/useECharts.ts b/web/src/composables/useECharts.ts new file mode 100644 index 000000000..f45815ef3 --- /dev/null +++ b/web/src/composables/useECharts.ts @@ -0,0 +1,53 @@ +import { init, type EChartsType } from 'echarts/core' +import type { EChartsOption } from 'echarts' +import { onBeforeUnmount, shallowRef, watch, type ShallowRef } from 'vue' + +import '@/plugins/echarts' + +export function useECharts( + target: Readonly>, + option: Readonly>, +) { + const chart = shallowRef() + const observer = shallowRef() + + function resize() { + chart.value?.resize() + } + + function dispose() { + observer.value?.disconnect() + chart.value?.dispose() + observer.value = undefined + chart.value = undefined + } + + watch( + target, + (element) => { + dispose() + if (!element) return + chart.value = init(element, undefined, { renderer: 'canvas' }) + chart.value.setOption(option.value) + observer.value = new ResizeObserver(resize) + observer.value.observe(element) + }, + { immediate: true }, + ) + + watch( + option, + (nextOption) => { + chart.value?.setOption(nextOption, true) + }, + { deep: true }, + ) + + onBeforeUnmount(dispose) + + return { + chart, + resize, + dispose, + } +} diff --git a/web/src/layout/components/AppSidebar.vue b/web/src/layout/components/AppSidebar.vue new file mode 100644 index 000000000..e026bd43a --- /dev/null +++ b/web/src/layout/components/AppSidebar.vue @@ -0,0 +1,73 @@ + + + diff --git a/web/src/layout/components/AppTopbar.vue b/web/src/layout/components/AppTopbar.vue new file mode 100644 index 000000000..8d88bbf0e --- /dev/null +++ b/web/src/layout/components/AppTopbar.vue @@ -0,0 +1,111 @@ + + + diff --git a/web/src/layout/index.vue b/web/src/layout/index.vue new file mode 100644 index 000000000..5938599b9 --- /dev/null +++ b/web/src/layout/index.vue @@ -0,0 +1,14 @@ + + + diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 000000000..672168ec8 --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,9 @@ +import { createApp } from 'vue' + +import App from './App.vue' +import { router } from './router' +import { pinia } from './stores' + +import './styles/index.css' + +createApp(App).use(pinia).use(router).mount('#app') diff --git a/web/src/plugins/echarts.ts b/web/src/plugins/echarts.ts new file mode 100644 index 000000000..936ca8fd5 --- /dev/null +++ b/web/src/plugins/echarts.ts @@ -0,0 +1,11 @@ +import { LineChart } from 'echarts/charts' +import { GridComponent, TooltipComponent } from 'echarts/components' +import { use } from 'echarts/core' +import { CanvasRenderer } from 'echarts/renderers' + +use([ + LineChart, + GridComponent, + TooltipComponent, + CanvasRenderer, +]) diff --git a/web/src/router/index.ts b/web/src/router/index.ts new file mode 100644 index 000000000..29d3acc69 --- /dev/null +++ b/web/src/router/index.ts @@ -0,0 +1,8 @@ +import { createRouter, createWebHistory } from 'vue-router' + +import { routes } from './routes' + +export const router = createRouter({ + history: createWebHistory('/admin/'), + routes, +}) diff --git a/web/src/router/routes.ts b/web/src/router/routes.ts new file mode 100644 index 000000000..8d0137177 --- /dev/null +++ b/web/src/router/routes.ts @@ -0,0 +1,22 @@ +import type { RouteRecordRaw } from 'vue-router' + +import AdminLayout from '@/layout/index.vue' +import DashboardView from '@/views/dashboard/DashboardView.vue' + +export const routes: RouteRecordRaw[] = [ + { + path: '/', + component: AdminLayout, + children: [ + { + path: '', + name: 'dashboard', + component: DashboardView, + }, + ], + }, + { + path: '/:pathMatch(.*)*', + redirect: '/', + }, +] diff --git a/web/src/stores/index.ts b/web/src/stores/index.ts new file mode 100644 index 000000000..c569ef7f2 --- /dev/null +++ b/web/src/stores/index.ts @@ -0,0 +1,3 @@ +import { createPinia } from 'pinia' + +export const pinia = createPinia() diff --git a/web/src/stores/modules/app.ts b/web/src/stores/modules/app.ts new file mode 100644 index 000000000..d3a3a8f6c --- /dev/null +++ b/web/src/stores/modules/app.ts @@ -0,0 +1,15 @@ +import { defineStore } from 'pinia' +import { shallowRef } from 'vue' + +export const useAppStore = defineStore('app', () => { + const sidebarCollapsed = shallowRef(false) + + function toggleSidebar() { + sidebarCollapsed.value = !sidebarCollapsed.value + } + + return { + sidebarCollapsed, + toggleSidebar, + } +}) diff --git a/web/src/stores/modules/session.ts b/web/src/stores/modules/session.ts new file mode 100644 index 000000000..3307f0ade --- /dev/null +++ b/web/src/stores/modules/session.ts @@ -0,0 +1,17 @@ +import { defineStore } from 'pinia' + +export interface SessionUser { + name: string + role: string +} + +export const useSessionStore = defineStore('session', () => { + const user: SessionUser = { + name: 'admin', + role: '管理员', + } + + return { + user, + } +}) diff --git a/web/src/styles/base.css b/web/src/styles/base.css new file mode 100644 index 000000000..7ae759623 --- /dev/null +++ b/web/src/styles/base.css @@ -0,0 +1,38 @@ +* { + box-sizing: border-box; +} + +html { + min-width: 360px; + background: var(--cp-bg-page); + color: var(--cp-text-primary); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + min-height: 100vh; + margin: 0; + background: var(--cp-bg-page); +} + +button, +input, +select, +textarea { + font: inherit; +} + +button { + cursor: pointer; +} + +button:disabled { + cursor: not-allowed; +} + +#app { + min-height: 100vh; +} diff --git a/web/src/styles/index.css b/web/src/styles/index.css new file mode 100644 index 000000000..a143158f2 --- /dev/null +++ b/web/src/styles/index.css @@ -0,0 +1,19 @@ +@import './tokens.css'; +@import './base.css'; +@import 'tailwindcss'; + +@theme { + --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-mono: "JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace; + --color-cp-page: var(--cp-bg-page); + --color-cp-surface: var(--cp-bg-surface); + --color-cp-subtle: var(--cp-bg-subtle); + --color-cp-muted: var(--cp-bg-muted); + --color-cp-primary: var(--cp-text-primary); + --color-cp-secondary: var(--cp-text-secondary); + --color-cp-info: var(--cp-info); + --color-cp-normal: var(--cp-normal); + --color-cp-success: var(--cp-success); + --color-cp-warning: var(--cp-warning); + --color-cp-danger: var(--cp-danger); +} diff --git a/web/src/styles/tokens.css b/web/src/styles/tokens.css new file mode 100644 index 000000000..dd2678d0f --- /dev/null +++ b/web/src/styles/tokens.css @@ -0,0 +1,106 @@ +:root { + color-scheme: light; + + --cp-bg-page: #f6f8fb; + --cp-bg-surface: #ffffff; + --cp-bg-subtle: #f8fafc; + --cp-bg-muted: #f1f5f9; + --cp-bg-nav-active: #e9eef5; + --cp-bg-dark: #111827; + --cp-white: #ffffff; + --cp-transparent: #ffffff00; + --cp-overlay-scrim: #0e17264d; + + --cp-text-primary: #0e1726; + --cp-text-strong: #111827; + --cp-text-secondary: #64748b; + --cp-text-muted: #94a3b8; + + --cp-default-bg: #f8fafc; + --cp-default-bg-hover: #f1f5f9; + --cp-default-bg-active: #e9eef5; + --cp-default-border: #e2e8f0; + --cp-default-border-hover: #cbd5e1; + --cp-default-text: #0e1726; + --cp-default-text-hover: #111827; + --cp-default-text-active: #0b1220; + + --cp-disabled-bg: #f1f5f9; + --cp-disabled-border: #e2e8f0; + --cp-disabled-text: #94a3b8; + --cp-disabled-icon: #cbd5e1; + + --cp-info: #2563eb; + --cp-info-hover: #1d4ed8; + --cp-info-pressed: #1e40af; + --cp-info-bg: #eef6ff; + --cp-info-bg-hover: #dbeafe; + --cp-info-bg-active: #bfdbfe; + --cp-info-border: #93c5fd; + --cp-info-text: #2563eb; + --cp-info-on: #ffffff; + + --cp-normal: #0f9f9a; + --cp-normal-hover: #0d8d88; + --cp-normal-pressed: #0b7370; + --cp-normal-bg: #ecfdfd; + --cp-normal-bg-hover: #ddf7f6; + --cp-normal-bg-active: #c7efee; + --cp-normal-border: #a7dfdd; + --cp-normal-text: #0b7370; + --cp-normal-on: #0b1220; + + --cp-success: #12b981; + --cp-success-hover: #0ea371; + --cp-success-pressed: #0b865f; + --cp-success-bg: #ecfdf5; + --cp-success-bg-hover: #ddfbea; + --cp-success-bg-active: #c8f5dc; + --cp-success-border: #b7ead0; + --cp-success-text: #047857; + --cp-success-on: #0b1220; + + --cp-warning: #f59e0b; + --cp-warning-hover: #d97706; + --cp-warning-pressed: #b45309; + --cp-warning-bg: #fffbeb; + --cp-warning-bg-hover: #fef3c7; + --cp-warning-bg-active: #fde68a; + --cp-warning-border: #fcd34d; + --cp-warning-text: #b45309; + --cp-warning-on: #0b1220; + + --cp-danger: #ef4444; + --cp-danger-hover: #dc2626; + --cp-danger-pressed: #b91c1c; + --cp-danger-bg: #fef2f2; + --cp-danger-bg-hover: #fee2e2; + --cp-danger-bg-active: #fecaca; + --cp-danger-border: #fca5a5; + --cp-danger-text: #b91c1c; + --cp-danger-on: #0b1220; + + --cp-control-height-small: 24px; + --cp-control-height-default: 32px; + --cp-control-height-large: 40px; + --cp-control-padding-x: 12px; + --cp-control-radius-small: 2px; + --cp-control-radius-base: 4px; + --cp-button-radius-small: 4px; + --cp-button-radius-base: 6px; + --cp-icon-button-radius: 8px; + --cp-card-radius: 18px; + --cp-panel-radius: 16px; + --cp-tag-radius: 7px; + --cp-radius-circle: 999px; + + --cp-shadow-card: 0 10px 22px -18px #0e172614; + --cp-shadow-control: 0 8px 18px -16px #0e172614; + --cp-shadow-popover: 0 16px 34px -18px #0e172622; + --cp-shadow-sidebar: 2px 0 12px -12px #0e172607; + + --cp-space-page-x: 28px; + --cp-space-page-y: 32px; + --cp-gap-card: 24px; + --cp-sidebar-width: 280px; +} diff --git a/web/src/views/dashboard/DashboardView.vue b/web/src/views/dashboard/DashboardView.vue new file mode 100644 index 000000000..4add10f51 --- /dev/null +++ b/web/src/views/dashboard/DashboardView.vue @@ -0,0 +1,49 @@ + + + diff --git a/web/src/views/dashboard/components/AccountOverviewCard.vue b/web/src/views/dashboard/components/AccountOverviewCard.vue new file mode 100644 index 000000000..b522c5ef2 --- /dev/null +++ b/web/src/views/dashboard/components/AccountOverviewCard.vue @@ -0,0 +1,184 @@ + + + diff --git a/web/src/views/dashboard/components/EventLogCard.vue b/web/src/views/dashboard/components/EventLogCard.vue new file mode 100644 index 000000000..0aaf5b9bc --- /dev/null +++ b/web/src/views/dashboard/components/EventLogCard.vue @@ -0,0 +1,91 @@ + + + diff --git a/web/src/views/dashboard/components/MetricCard.vue b/web/src/views/dashboard/components/MetricCard.vue new file mode 100644 index 000000000..0059f269c --- /dev/null +++ b/web/src/views/dashboard/components/MetricCard.vue @@ -0,0 +1,49 @@ + + + diff --git a/web/src/views/dashboard/components/RequestTrendCard.vue b/web/src/views/dashboard/components/RequestTrendCard.vue new file mode 100644 index 000000000..71c851c94 --- /dev/null +++ b/web/src/views/dashboard/components/RequestTrendCard.vue @@ -0,0 +1,95 @@ + + + diff --git a/web/src/views/dashboard/components/ServiceStatusCard.vue b/web/src/views/dashboard/components/ServiceStatusCard.vue new file mode 100644 index 000000000..ab9c25260 --- /dev/null +++ b/web/src/views/dashboard/components/ServiceStatusCard.vue @@ -0,0 +1,44 @@ + + + diff --git a/web/src/views/dashboard/composables/useDashboard.ts b/web/src/views/dashboard/composables/useDashboard.ts new file mode 100644 index 000000000..d1a0a3cb5 --- /dev/null +++ b/web/src/views/dashboard/composables/useDashboard.ts @@ -0,0 +1,188 @@ +import { + Activity, + Boxes, + CloudCheck, + FileText, + MonitorCheck, + RefreshCw, + ScrollText, + Timer, + Users, +} from '@lucide/vue' + +import type { + AccountUsageItem, + EventLogItem, + MetricCardItem, + ServiceStatusItem, + TrendPoint, + TrendSummaryItem, +} from '../types' + +export function useDashboard() { + const metrics: MetricCardItem[] = [ + { + title: '账号', + value: '33', + icon: Users, + tone: 'normal', + details: [ + { label: '启用', value: '1', tone: 'normal' }, + { label: '错误', value: '31', tone: 'danger' }, + ], + }, + { + title: '今日请求', + value: '0', + icon: Activity, + tone: 'info', + details: [ + { label: '今日', value: '0', tone: 'info' }, + { label: '总计', value: '3,363', tone: 'info' }, + ], + }, + { + title: '总 Token', + value: '401.29M', + icon: FileText, + tone: 'success', + details: [ + { label: '今日', value: '0', tone: 'success' }, + { label: '总计', value: '401.29M', tone: 'success' }, + ], + }, + { + title: '平均响应', + value: '21.71s', + icon: Timer, + tone: 'warning', + details: [ + { label: '今日', value: '21.71s', tone: 'warning' }, + { label: '目标', value: '< 3s', tone: 'warning' }, + ], + }, + ] + + const trendPoints: TrendPoint[] = [ + { time: '00', requests: 0, tokens: 0, errors: 0 }, + { time: '04', requests: 0, tokens: 0, errors: 0 }, + { time: '08', requests: 0, tokens: 0, errors: 0 }, + { time: '12', requests: 0, tokens: 0, errors: 0 }, + { time: '16', requests: 0, tokens: 0, errors: 0 }, + { time: '20', requests: 0, tokens: 0, errors: 0 }, + { time: '24', requests: 0, tokens: 0, errors: 0 }, + ] + + const trendSummary: TrendSummaryItem[] = [ + { label: '成功率', value: '99.88%', tone: 'success' }, + { label: '峰值', value: '15.2K', tone: 'info' }, + { label: '慢请求', value: '42', tone: 'warning' }, + ] + + const accountUsage: AccountUsageItem[] = [ + { + name: 'Amy Ops', + email: 'amy.ops@example.com', + plan: 'plus', + requests: '12.8K', + tokens: '382K', + lastUsed: '2分钟前', + tone: 'info', + loadWidth: 84, + }, + { + name: 'Team Codex', + email: 'team-codex@example.com', + plan: 'pro', + requests: '9.4K', + tokens: '241K', + lastUsed: '8分钟前', + tone: 'normal', + loadWidth: 70, + }, + { + name: 'Build Bot', + email: 'build@proxy.local', + plan: 'team', + requests: '7.1K', + tokens: '198K', + lastUsed: '16分钟前', + tone: 'success', + loadWidth: 58, + }, + { + name: 'Reviewer', + email: 'reviewer@example.com', + plan: 'plus', + requests: '6.8K', + tokens: '176K', + lastUsed: '24分钟前', + tone: 'warning', + loadWidth: 48, + }, + ] + + const serviceStatuses: ServiceStatusItem[] = [ + { label: '上游连接', value: '正常', detail: '23ms', tone: 'success', icon: CloudCheck }, + { label: '模型目录', value: '已同步', detail: '58 个模型', tone: 'info', icon: Boxes }, + { label: '自动刷新', value: '开启', detail: '2 并发', tone: 'success', icon: RefreshCw }, + { label: '事件记录', value: '关闭', detail: '已存 1.8K', tone: 'warning', icon: ScrollText }, + { label: '客户端版本', value: '可用', detail: '26.519', tone: 'normal', icon: MonitorCheck }, + ] + + const eventLogs: EventLogItem[] = [ + { + id: 'evt_01', + time: '14:30:25', + level: '信息', + requestId: 'req_01HW9K7QY2', + route: '/v1/responses', + model: 'gpt-5.5', + statusCode: '200', + latency: '1.25s', + tone: 'info', + }, + { + id: 'evt_02', + time: '14:30:15', + level: '警告', + requestId: 'req_01HW9K6N4F', + route: '/v1/chat/completions', + model: 'gpt-5.5', + statusCode: '429', + latency: '2.15s', + tone: 'warning', + }, + { + id: 'evt_03', + time: '14:30:10', + level: '错误', + requestId: 'req_01HW9K5Z7A', + route: '/v1/responses', + model: 'gpt-5.5', + statusCode: '502', + latency: '5.32s', + tone: 'danger', + }, + { + id: 'evt_04', + time: '14:29:56', + level: '信息', + requestId: 'req_01HW9K4C3Q', + route: '/v1/models', + model: 'catalog', + statusCode: '200', + latency: '0.84s', + tone: 'info', + }, + ] + + return { + metrics, + trendPoints, + trendSummary, + accountUsage, + serviceStatuses, + eventLogs, + } +} diff --git a/web/src/views/dashboard/types.ts b/web/src/views/dashboard/types.ts new file mode 100644 index 000000000..1a1cac52a --- /dev/null +++ b/web/src/views/dashboard/types.ts @@ -0,0 +1,61 @@ +import type { Component } from 'vue' + +export type SemanticTone = 'normal' | 'info' | 'success' | 'warning' | 'danger' + +export interface MetricDetail { + label: string + value: string + tone?: SemanticTone +} + +export interface MetricCardItem { + title: string + value: string + icon: Component + tone: SemanticTone + details: MetricDetail[] +} + +export interface TrendPoint { + time: string + requests: number + tokens: number + errors: number +} + +export interface TrendSummaryItem { + label: string + value: string + tone: SemanticTone +} + +export interface AccountUsageItem { + name: string + email: string + plan: string + requests: string + tokens: string + lastUsed: string + tone: SemanticTone + loadWidth: number +} + +export interface ServiceStatusItem { + label: string + value: string + detail: string + tone: SemanticTone + icon: Component +} + +export interface EventLogItem { + id: string + time: string + level: string + requestId: string + route: string + model: string + statusCode: string + latency: string + tone: SemanticTone +} diff --git a/web/tests/dashboard-design.test.mjs b/web/tests/dashboard-design.test.mjs new file mode 100644 index 000000000..3628a0762 --- /dev/null +++ b/web/tests/dashboard-design.test.mjs @@ -0,0 +1,176 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { test } from 'node:test' +import { fileURLToPath } from 'node:url' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') + +async function source(path) { + return readFile(resolve(root, path), 'utf8') +} + +function assertTokens(sourceText, tokens) { + for (const token of tokens) { + assert.match( + sourceText, + new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + `Expected token: ${token}`, + ) + } +} + +function assertNoAbsoluteLayout(sourceText, label) { + assert.doesNotMatch( + sourceText, + /\babsolute\b/, + `${label} should use grid/flex/margin/padding flow layout, not absolute positioning`, + ) +} + +test('dashboard page recreates the Pencil canvas with flow layout spacing', async () => { + const view = await source('src/views/dashboard/DashboardView.vue') + + assertNoAbsoluteLayout(view, 'DashboardView') + assertTokens(view, [ + 'ml-7 w-[1584px] pt-[34px] pb-[60px]', + 'flex h-[68px] items-start justify-between', + 'mt-0 text-[34px] leading-[1.15] font-[800]', + 'mt-2.5 text-[15px] leading-[1.15] font-semibold', + 'mt-6 grid grid-cols-4 gap-6', + 'mt-6 grid grid-cols-[948px_608px] gap-7', + 'mt-6', + 'MetricCard', + 'RequestTrendCard', + 'ServiceStatusCard', + 'AccountOverviewCard', + 'EventLogCard', + ]) +}) + +test('topbar controls are normal flex items and only the popover floats', async () => { + const topbar = await source('src/layout/components/AppTopbar.vue') + + assert.doesNotMatch(topbar, /absolute left-\[/) + assertTokens(topbar, [ + 'flex h-11 items-start gap-3.5', + 'h-11 w-[170px]', + 'size-11', + 'h-11 w-[216px]', + 'relative', + 'absolute right-0 top-14', + 'h-[304px] w-80', + 'CalendarDays', + 'RefreshCw', + 'ChevronUp', + 'User', + 'ShieldCheck', + 'ScrollText', + 'LogOut', + 'MFA', + '已开启', + '会话', + '1 台', + ]) +}) + +test('dashboard cards use grid and flex primitives with exact Pencil dimensions', async () => { + const metric = await source('src/views/dashboard/components/MetricCard.vue') + const trend = await source('src/views/dashboard/components/RequestTrendCard.vue') + const service = await source('src/views/dashboard/components/ServiceStatusCard.vue') + const account = await source('src/views/dashboard/components/AccountOverviewCard.vue') + const events = await source('src/views/dashboard/components/EventLogCard.vue') + + for (const [label, file] of [ + ['MetricCard', metric], + ['RequestTrendCard', trend], + ['ServiceStatusCard', service], + ['AccountOverviewCard', account], + ['EventLogCard', events], + ]) { + assertNoAbsoluteLayout(file, label) + } + + assertTokens(metric, [ + 'h-[154px] w-[378px]', + 'px-6 pt-5', + 'flex items-start gap-3', + 'mt-2.5', + 'mt-[16.6px]', + 'h-[30px] w-[330px]', + ]) + + assertTokens(trend, [ + 'h-[380px] w-[948px]', + 'px-7 pt-[22px]', + 'flex items-start justify-between', + 'h-[38px] w-[246px]', + 'mt-7 grid grid-cols-[682px_180px] gap-[30px]', + 'h-[268px] w-[682px]', + 'M0 150c70-14 96-20 136-38 48-24 74-20 108-58 42-42 78-22 116 6 44 32 82 18 124-10 44-24 82-16 120 30 18 22 34 12 46-6', + 'M0 70c80-2 122-8 170-6 56 2 116-14 166-8 62 6 94 18 142 2 48-16 108-2 172-10', + 'M0 44c72 4 116-8 174-4 58 2 110-6 166-2 66 8 118-8 174-4 60 6 100-4 136-8', + ]) + + assertTokens(service, [ + 'h-[380px] w-[608px]', + 'px-7 pt-6', + 'mt-[23px] grid gap-2', + 'h-[50px] w-[552px]', + ]) + + assertTokens(account, [ + 'h-[450px] w-[1584px]', + 'grid grid-cols-[360px_556px_556px] gap-7 px-7 pt-6', + 'h-[402px] w-[360px]', + 'h-[330px] w-[556px]', + 'h-[402px] w-[556px]', + '81 / 135', + '60% 已分配', + '最少使用优先', + '71.1%', + ]) + + assertTokens(events, [ + 'h-[350px] w-[1584px]', + 'px-7 pt-6', + 'flex items-start justify-between', + 'h-9 w-[206px]', + 'mt-[26px] h-10 w-[1528px]', + 'mt-4 flex h-[184px] w-[1528px] justify-between overflow-hidden', + '请求 ID', + ]) +}) + +test('dashboard data mirrors Pencil text values', async () => { + const data = await source('src/views/dashboard/composables/useDashboard.ts') + + assertTokens(data, [ + '启用', + '错误', + '3,363', + '< 3s', + '成功率', + '慢请求', + 'Amy Ops', + 'Team Codex', + 'Build Bot', + 'Reviewer', + 'team-codex@example.com', + '24分钟前', + '上游连接', + '模型目录', + '自动刷新', + '事件记录', + '客户端版本', + 'CloudCheck', + 'Boxes', + 'MonitorCheck', + 'req_01HW9K7QY2', + 'req_01HW9K6N4F', + 'req_01HW9K5Z7A', + 'req_01HW9K4C3Q', + '/v1/chat/completions', + 'catalog', + ]) +}) diff --git a/web/tests/sidebar-design.test.mjs b/web/tests/sidebar-design.test.mjs new file mode 100644 index 000000000..aaf3d301d --- /dev/null +++ b/web/tests/sidebar-design.test.mjs @@ -0,0 +1,63 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import { test } from 'node:test' +import { fileURLToPath } from 'node:url' +import { dirname, resolve } from 'node:path' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const sidebarPath = resolve(root, 'src/layout/components/AppSidebar.vue') + +test('sidebar uses Pencil design icons and brand copy', async () => { + const source = await readFile(sidebarPath, 'utf8') + + for (const token of [ + 'SquareTerminal', + 'LayoutDashboard', + 'Users', + 'KeyRound', + 'ScrollText', + 'ChartColumn', + 'Box', + 'Settings', + 'Radar', + 'PanelLeftClose', + 'Proxy RS · v0.1.0', + ]) { + assert.match(source, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) + } +}) + +test('sidebar locks core Pencil dimensions and coordinates', async () => { + const source = await readFile(sidebarPath, 'utf8') + + for (const token of [ + 'w-[280px]', + 'basis-[280px]', + 'left-[28px]', + 'top-[31px]', + 'size-[46px]', + 'left-[84px]', + 'top-[34px]', + 'top-[57px]', + 'left-6', + 'w-[232px]', + 'h-[46px]', + 'left-[22px]', + 'top-[13px]', + 'left-[54px]', + 'top-[15px]', + 'top-[112px]', + 'top-[170px]', + 'top-[228px]', + 'top-[286px]', + 'top-[344px]', + 'top-[402px]', + 'top-[460px]', + 'top-[518px]', + 'left-[220px]', + 'bottom-8', + 'size-9', + ]) { + assert.match(source, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) + } +}) diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json new file mode 100644 index 000000000..994988e0b --- /dev/null +++ b/web/tsconfig.app.json @@ -0,0 +1,20 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "target": "ES2024", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 000000000..1ffef600d --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json new file mode 100644 index 000000000..b2564b73f --- /dev/null +++ b/web/tsconfig.node.json @@ -0,0 +1,16 @@ +{ + "include": ["vite.config.ts"], + "compilerOptions": { + "composite": true, + "target": "ES2024", + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "types": ["node"] + } +} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 000000000..5dc4104db --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,37 @@ +import tailwindcss from '@tailwindcss/vite' +import vue from '@vitejs/plugin-vue' +import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vite' + +export default defineConfig({ + base: '/admin/', + plugins: [vue(), tailwindcss()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + port: 5173, + proxy: { + '/api': 'http://127.0.0.1:8080', + '/auth': 'http://127.0.0.1:8080', + }, + }, + build: { + outDir: 'dist', + assetsDir: 'assets', + rolldownOptions: { + output: { + codeSplitting: { + groups: [ + { + name: 'echarts', + test: /node_modules\/echarts|node_modules\/zrender/, + }, + ], + }, + }, + }, + }, +})