diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4b93e9a --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY=sk-votre-cle-api-ici diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6a4646 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.next/ +.env.local +.DS_Store diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..830fb59 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..91ef62f --- /dev/null +++ b/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +}; + +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d25b3f0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3005 @@ +{ + "name": "git-statistics-dashboard", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "git-statistics-dashboard", + "version": "1.0.0", + "dependencies": { + "@radix-ui/react-slot": "^1.2.4", + "@types/node": "^20.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "autoprefixer": "^10.4.16", + "chart.js": "^4.5.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.0", + "date-fns": "^3.2.0", + "dotenv": "^16.4.5", + "lucide-react": "^0.344.0", + "next": "^15.1.0", + "openai": "^4.28.0", + "react": "^19.0.0", + "react-chartjs-2": "^5.3.1", + "react-dom": "^19.0.0", + "recharts": "^2.12.2", + "simple-git": "^3.22.0", + "tailwind-merge": "^2.2.1", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "tailwindcss": "^3.4.1", + "typescript": "^5.3.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, + "node_modules/@next/env": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.15.tgz", + "integrity": "sha512-vcmyu5/MyFzN7CdqRHO3uHO44p/QPCZkuTUXroeUmhNP8bL5PHFEhik22JUazt+CDDoD6EpBYRCaS2pISL+/hg==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.15.tgz", + "integrity": "sha512-6PvFO2Tzt10GFK2Ro9tAVEtacMqRmTarYMFKAnV2vYMdwWc73xzmDQyAV7SwEdMhzmiRoo7+m88DuiXlJlGeaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.15.tgz", + "integrity": "sha512-G+YNV+z6FDZTp/+IdGyIMFqalBTaQSnvAA+X/hrt+eaTRFSznRMz9K7rTmzvM6tDmKegNtyzgufZW0HwVzEqaQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.15.tgz", + "integrity": "sha512-eVkrMcVIBqGfXB+QUC7jjZ94Z6uX/dNStbQFabewAnk13Uy18Igd1YZ/GtPRzdhtm7QwC0e6o7zOQecul4iC1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.15.tgz", + "integrity": "sha512-RwSHKMQ7InLy5GfkY2/n5PcFycKA08qI1VST78n09nN36nUPqCvGSMiLXlfUmzmpQpF6XeBYP2KRWHi0UW3uNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.15.tgz", + "integrity": "sha512-nplqvY86LakS+eeiuWsNWvfmK8pFcOEW7ZtVRt4QH70lL+0x6LG/m1OpJ/tvrbwjmR8HH9/fH2jzW1GlL03TIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.15.tgz", + "integrity": "sha512-eAgl9NKQ84/sww0v81DQINl/vL2IBxD7sMybd0cWRw6wqgouVI53brVRBrggqBRP/NWeIAE1dm5cbKYoiMlqDQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.15.tgz", + "integrity": "sha512-GJVZC86lzSquh0MtvZT+L7G8+jMnJcldloOjA8Kf3wXvBrvb6OGe2MzPuALxFshSm/IpwUtD2mIoof39ymf52A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.15.tgz", + "integrity": "sha512-nFucjVdwlFqxh/JG3hWSJ4p8+YJV7Ii8aPDuBQULB6DzUF4UNZETXLfEUk+oI2zEznWWULPt7MeuTE6xtK1HSA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", + "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz", + "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001790", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz", + "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "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==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "peer": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lucide-react": { + "version": "0.344.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.344.0.tgz", + "integrity": "sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/next": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.15.tgz", + "integrity": "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.15", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.15", + "@next/swc-darwin-x64": "15.5.15", + "@next/swc-linux-arm64-gnu": "15.5.15", + "@next/swc-linux-arm64-musl": "15.5.15", + "@next/swc-linux-x64-gnu": "15.5.15", + "@next/swc-linux-x64-musl": "15.5.15", + "@next/swc-win32-arm64-msvc": "15.5.15", + "@next/swc-win32-x64-msvc": "15.5.15", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "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", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-chartjs-2": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", + "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.5" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/simple-git": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", + "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "@simple-git/args-pathspec": "^1.0.3", + "@simple-git/argv-parser": "^1.1.0", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3bec38c --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "git-statistics-dashboard", + "version": "1.0.0", + "description": "Dashboard pour les statistiques de commits Git", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@radix-ui/react-slot": "^1.2.4", + "@types/node": "^20.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "autoprefixer": "^10.4.16", + "chart.js": "^4.5.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.0", + "date-fns": "^3.2.0", + "dotenv": "^16.4.5", + "lucide-react": "^0.344.0", + "next": "^15.1.0", + "openai": "^4.28.0", + "react": "^19.0.0", + "react-chartjs-2": "^5.3.1", + "react-dom": "^19.0.0", + "recharts": "^2.12.2", + "simple-git": "^3.22.0", + "tailwind-merge": "^2.2.1", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "tailwindcss": "^3.4.1", + "typescript": "^5.3.3" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/src/app/api/fs/browse/route.ts b/src/app/api/fs/browse/route.ts new file mode 100644 index 0000000..13131c6 --- /dev/null +++ b/src/app/api/fs/browse/route.ts @@ -0,0 +1,47 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; +import { readdirSync, statSync, existsSync } from "fs"; +import { join, resolve, sep, dirname } from "path"; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams; + const dirPath = searchParams.get("path") || ""; + + try { + const resolved = dirPath ? resolve(dirPath) : resolve("/"); + if (!existsSync(resolved)) { + return NextResponse.json({ error: "Dossier introuvable" }, { status: 404 }); + } + + const stat = statSync(resolved); + if (!stat.isDirectory()) { + return NextResponse.json({ error: "Ce n'est pas un dossier" }, { status: 400 }); + } + + const entries = readdirSync(resolved, { withFileTypes: true }); + const folders = entries + .filter((e) => e.isDirectory() && !e.name.startsWith(".")) + .map((e) => { + const full = join(resolved, e.name); + const isGitRepo = existsSync(join(full, ".git")); + return { name: e.name, path: full, isGitRepo }; + }) + .sort((a, b) => a.name.localeCompare(b.name)); + + const parent = dirname(resolved); + const isRoot = resolved === sep || resolved.match(/^[A-Z]:\\$/i); + const parentPath = isRoot ? null : parent; + + return NextResponse.json({ + path: resolved, + parentPath, + isRoot, + folders, + }); + } catch (err: any) { + if (err.code === "EACCES" || err.code === "EPERM") { + return NextResponse.json({ error: "Permission refusée" }, { status: 403 }); + } + return NextResponse.json({ error: err?.message || "Erreur" }, { status: 500 }); + } +} diff --git a/src/app/api/settings/route.ts b/src/app/api/settings/route.ts new file mode 100644 index 0000000..5badc7c --- /dev/null +++ b/src/app/api/settings/route.ts @@ -0,0 +1,32 @@ +import { NextResponse } from "next/server"; + +const OLLAMA_URL = process.env.OLLAMA_BASE_URL?.replace(/\/v1$/, "") || "http://localhost:11434"; + +export async function GET() { + let models: string[] = []; + let detectedModel = process.env.OLLAMA_MODEL || null; + + try { + const res = await fetch(`${OLLAMA_URL}/api/tags`, { signal: AbortSignal.timeout(3000) }); + if (res.ok) { + const data = await res.json(); + models = (data.models || []).map((m: any) => m.name); + if (!detectedModel && models.length > 0) { + // Auto-pick: prefer smaller models first + const preferred = ["gemma4", "gemma3", "gemma", "llama3.2", "llama3", "phi", "qwen", "mistral"]; + for (const p of preferred) { + const match = models.find((m: string) => m.toLowerCase().includes(p)); + if (match) { detectedModel = match; break; } + } + if (!detectedModel) detectedModel = models[0]; + } + } + } catch { /* offline */ } + + return NextResponse.json({ + model: detectedModel || "?", + models, + autoDetected: !process.env.OLLAMA_MODEL, + ollamaUrl: OLLAMA_URL, + }); +} diff --git a/src/app/api/stats/route.ts b/src/app/api/stats/route.ts new file mode 100644 index 0000000..c5fbf40 --- /dev/null +++ b/src/app/api/stats/route.ts @@ -0,0 +1,24 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams; + const repoPath = searchParams.get("path"); + const days = parseInt(searchParams.get("days") || "365"); + const maxCommits = parseInt(searchParams.get("maxCommits") || "5000"); + + if (!repoPath) { + return NextResponse.json({ error: "Path missing" }, { status: 400 }); + } + + try { + const { analyzeGitRepo } = await import("@/lib/git-analyzer"); + const stats = await analyzeGitRepo(repoPath, "main", days, { + maxCommits, + skipAI: true, // AI is done separately for speed + }); + return NextResponse.json({ stats }); + } catch (error: any) { + return NextResponse.json({ error: error?.message || "Analyse échouée" }, { status: 500 }); + } +} diff --git a/src/app/api/summary/route.ts b/src/app/api/summary/route.ts new file mode 100644 index 0000000..e725aeb --- /dev/null +++ b/src/app/api/summary/route.ts @@ -0,0 +1,19 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { stats, model } = body; + if (!stats || !stats.repoName) { + return NextResponse.json({ error: "Stats manquantes" }, { status: 400 }); + } + + const { generateAISummary } = await import("@/lib/git-analyzer"); + const summary = await generateAISummary(stats, model || undefined); + + return NextResponse.json({ summary }); + } catch (error: any) { + return NextResponse.json({ error: error?.message || "Erreur" }, { status: 500 }); + } +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..a652793 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,80 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 48%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + } +} + +@layer components { + .scrollbar-thin { + scrollbar-width: thin; + } + .scrollbar-thin::-webkit-scrollbar { + width: 6px; + height: 6px; + } + .scrollbar-thin::-webkit-scrollbar-track { + background: transparent; + } + .scrollbar-thin::-webkit-scrollbar-thumb { + background: hsl(var(--muted-foreground) / 0.3); + border-radius: 3px; + } + .scrollbar-thin::-webkit-scrollbar-thumb:hover { + background: hsl(var(--muted-foreground) / 0.5); + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..3285783 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,24 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); + +export const metadata: Metadata = { + title: "Tableau de Bord Statistiques Git", + description: "Visualisez les statistiques de votre dépôt Git", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +
{children}
+ + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..ace819b --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,419 @@ +"use client"; + +import { useState, useRef, useEffect, useMemo } from "react"; +import { Button } from "@/components/ui/button"; +import { StatsOverview } from "@/components/dashboard"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { + BarChart2, GitBranch, Loader2, AlertCircle, TrendingUp, Users, + PieChart, Calendar, X, FolderOpen, Search, MessageSquare, FileText, + Settings, Bot, Cpu, Sun, Moon, Sparkles, ChevronRight, RefreshCw +} from "lucide-react"; + +const COLORS = ["#6366f1", "#f59e0b", "#10b981", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899", "#f97316", "#84cc16", "#64748b"]; + +export default function Home() { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [dialogOpen, setDialogOpen] = useState(false); + const [ollamaStatus, setOllamaStatus] = useState<"checking" | "online" | "offline">("checking"); + const [ollamaModel, setOllamaModel] = useState("..."); + const [ollamaModels, setOllamaModels] = useState([]); + const [settingsOpen, setSettingsOpen] = useState(false); + const [aiSummary, setAiSummary] = useState(null); + const [summaryLoading, setSummaryLoading] = useState(false); + const [summaryError, setSummaryError] = useState(null); + const [dark, setDark] = useState(false); + const statsRef = useRef(null); + + useEffect(() => { + fetch("/api/settings").then(r => r.json()).then(d => { + setOllamaModel(d.model || "?"); + setOllamaModels(d.models || []); + if (d.models?.length > 0) setOllamaStatus("online"); + }).catch(() => {}); + if (window.matchMedia("(prefers-color-scheme: dark)").matches) setDark(true); + }, []); + + useEffect(() => { document.documentElement.classList.toggle("dark", dark); }, [dark]); + + const analyzeRepo = async (repoPath: string) => { + setLoading(true); setError(null); setAiSummary(null); setSummaryError(null); + try { + const res = await fetch(`/api/stats?path=${encodeURIComponent(repoPath)}`); + const data = await res.json(); + if (data.error) throw new Error(data.error); + setStats(data.stats); + statsRef.current = data.stats; + generateSummary(data.stats, ollamaModel !== "?" ? ollamaModel : undefined); + } catch (e: any) { + setError(e?.message || "Erreur"); + } finally { + setLoading(false); + } + }; + + const generateSummary = async (s: any, model?: string) => { + setSummaryLoading(true); setSummaryError(null); + // Strip heavy fields to avoid huge request body + const light = { ...s }; + if (light.recentCommits) { + light.recentCommits = light.recentCommits.map((c: any) => ({ ...c, filesList: undefined })); + } + try { + const res = await fetch("/api/summary", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ stats: light, model }), + }); + const d = await res.json(); + if (d.error) { setSummaryError(d.error); setAiSummary(null); } + else setAiSummary(d.summary); + } catch { + setSummaryError("Ollama injoignable. Lancez `ollama serve`."); + } finally { + setSummaryLoading(false); + } + }; + + return ( +
+ {/* Header */} +
+
+
+
+ +
+

GitPulse

+
+
+
+
+ +
+ + +
+
+
+ + setDialogOpen(false)} onSubmit={(p) => { setDialogOpen(false); analyzeRepo(p); }} /> + setSettingsOpen(false)} model={ollamaModel} models={ollamaModels} ollamaStatus={ollamaStatus} onSelectModel={(m) => { setOllamaModel(m); if (statsRef.current) generateSummary(statsRef.current, m); setSettingsOpen(false); }} /> + +
+ {loading && ( +
+
+
+ +
+

Analyse en cours...

+
+ )} + + {error && ( + + +

Erreur

{error}

+ +
+
+ )} + + {!loading && !stats && !error && ( +
+
+
+ +
+
+ +
+
+
+

GitPulse

+

+ Visualisation puissante de vos dépôts Git. Statistiques, tendances, contributeurs — le tout enrichi par IA. +

+
+ +
+ )} + + {!loading && stats && ( +
+ {/* Project header */} +
+
+

{stats.repoName || "Projet"}

+

{stats.repoPath}

+
+
+ + +
+
+ + {/* Stats cards */} + + + {/* AI Summary banner */} + {summaryLoading && ( + + + +

Résumé IA en cours

Modèle : {ollamaModel}

+
+
+ )} + {!summaryLoading && summaryError && ( + + + +

{summaryError}

+ +
+
+ )} + {!summaryLoading && !summaryError && aiSummary && ( + + + + + Analyse IA · {ollamaModel} + + +
{aiSummary}
+
+ )} + {!summaryLoading && !summaryError && !aiSummary && ollamaStatus === "offline" && ( + + + + Ollama hors ligne — ollama serve puis ollama pull llama3.2 pour activer l'IA + + + )} + + {/* Tabs */} + + + Commits + Graphiques + Contributeurs + Fichiers + Types + + +
+ + + + Historique ({stats.recentCommits?.length || 0} commits) + + + + + + +
+ + Activité quotidienne +
+
+ + Heatmap + + + + Par mois +
+
+
+
+ + +
+ Top contributeurs + + Détails + +
+ + + + + + +
+
+
+
+
+ + + + Fichiers les plus modifiés + + + {(stats.fileChanges || []).slice(0, 25).map((f: any) => ())}{(stats.fileChanges || []).length === 0 && ()}
Fichier+TotalCommits
{f.path}{f.additions.toLocaleString("fr-FR")}{f.deletions.toLocaleString("fr-FR")}{f.totalChanges.toLocaleString("fr-FR")}{f.commits}
Aucune donnée
+
+
+
+ + +
+ Répartition + Distribution
+
+
+
+
+
+ )} +
+
+ ); +} + +// ==================== Settings Dialog ==================== +function SettingsDialog({ open, onClose, model, models, ollamaStatus, onSelectModel }: { open: boolean; onClose: () => void; model: string; models: string[]; ollamaStatus: string; onSelectModel: (m: string) => void }) { + if (!open) return null; + return ( +
+
+
+
+

Modèle IA

+ +
+
+
+
+ {ollamaStatus === "online" ? "Ollama connecté" : "Ollama hors ligne"} +
+ {models.map((m) => ( + + ))} + {models.length === 0 &&

Aucun modèle. ollama pull llama3.2

} +
+
+
+ ); +} + +// ==================== AddRepoDialog ==================== +function AddRepoDialog({ open, onClose, onSubmit }: { open: boolean; onClose: () => void; onSubmit: (path: string) => void }) { + const [path, setPath] = useState(""); const [browsePath, setBrowsePath] = useState(""); + const [folders, setFolders] = useState<{ name: string; path: string; isGitRepo: boolean }[]>([]); + const [browseLoading, setBrowseLoading] = useState(false); const [browseError, setBrowseError] = useState(null); + const [parentPath, setParentPath] = useState(null); const inputRef = useRef(null); + useEffect(() => { if (open) { setPath(""); setTimeout(() => inputRef.current?.focus(), 100); } }, [open]); + useEffect(() => { const h = (e: KeyboardEvent) => { if (e.key === "Escape" && open) onClose(); }; document.addEventListener("keydown", h); return () => document.removeEventListener("keydown", h); }, [open, onClose]); + const fetchBrowse = async (dirPath: string) => { setBrowseLoading(true); setBrowseError(null); try { const res = await fetch(`/api/fs/browse?path=${encodeURIComponent(dirPath)}`); const data = await res.json(); if (data.error) throw new Error(data.error); setBrowsePath(data.path); setParentPath(data.parentPath); setFolders(data.folders.sort((a: any, b: any) => { if (a.isGitRepo && !b.isGitRepo) return -1; if (!a.isGitRepo && b.isGitRepo) return 1; return a.name.localeCompare(b.name); })); } catch (e: any) { setBrowseError(e?.message || "Erreur"); } finally { setBrowseLoading(false); } }; + useEffect(() => { if (open) fetchBrowse(path.trim() || "/"); }, [open]); + if (!open) return null; + const gitRepos = folders.filter((f: any) => f.isGitRepo), regularDirs = folders.filter((f: any) => !f.isGitRepo); + return ( +
+
+
+
+

Sélectionner un dépôt

+ +
+
+
+ Chemin : +
+ {parentPath !== null && ( + + )} + + {browsePath !== "/" && (<>{browsePath.split(/[\/\\]/).slice(1).join(" › ")})} +
+
+
{ e.preventDefault(); const t = path.trim(); if (t) onSubmit(t); }} className="flex gap-2"> + setPath(e.target.value)} placeholder="Collez ou tapez un chemin..." className="flex-1 rounded-md border border-input bg-background px-3 py-1.5 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring" autoComplete="off" /> + +
+
{browseLoading &&
Chargement...
}{browseError &&

{browseError}

}{!browseLoading && !browseError && folders.length === 0 && (

Dossier vide

)}{!browseLoading && !browseError && (
{gitRepos.length > 0 && (

Dépôts Git ({gitRepos.length})

{gitRepos.map((f: any) => ())}
)}{regularDirs.length > 0 && (
{gitRepos.length > 0 &&

Dossiers ({regularDirs.length})

}
{regularDirs.map((f: any) => ())}
)}
)}
); +} + +// ==================== Commits ==================== +function CommitFeed({ commits }: { commits: any[] }) { + if (!commits?.length) return

Aucun commit.

; + const getType = (msg: string) => { const m = msg.toLowerCase(); if (m.startsWith("feat")) return { l: "feat", c: "bg-emerald-500/10 text-emerald-600 ring-emerald-500/20" }; if (m.startsWith("fix")) return { l: "fix", c: "bg-orange-500/10 text-orange-600 ring-orange-500/20" }; if (m.startsWith("refactor")) return { l: "refactor", c: "bg-purple-500/10 text-purple-600 ring-purple-500/20" }; if (m.startsWith("docs")) return { l: "docs", c: "bg-blue-500/10 text-blue-600 ring-blue-500/20" }; if (m.startsWith("chore")) return { l: "chore", c: "bg-gray-500/10 text-gray-600 ring-gray-500/20" }; if (m.startsWith("perf")) return { l: "perf", c: "bg-red-500/10 text-red-600 ring-red-500/20" }; if (m.startsWith("test")) return { l: "test", c: "bg-yellow-500/10 text-yellow-600 ring-yellow-500/20" }; return { l: "", c: "" }; }; + return (
{commits.map((c, i) => { const t = getType(c.message || ""); return (
{t.l && {t.l}}{c.message || "(vide)"}
{c.author}{new Date(c.date).toLocaleDateString("fr-FR", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" })}{c.insertions > 0 && +{c.insertions}}{c.deletions > 0 && -{c.deletions}}{c.files > 0 && {c.files}f}
{c.shortHash}
); })}
); +} + +function CommitSearch({ commits }: { commits: any[] }) { + const [search, setSearch] = useState(""); const [authorFilter, setAuthorFilter] = useState(""); const [typeFilter, setTypeFilter] = useState(""); + const authors = useMemo(() => [...new Set(commits.map((c: any) => c.author))].sort(), [commits]); + const types = useMemo(() => [...new Set(commits.map((c: any) => (c.message || "").split(":")[0]?.split("(")[0] || "other"))].sort(), [commits]); + const filtered = useMemo(() => commits.filter((c: any) => { if (search && !(c.message || "").toLowerCase().includes(search.toLowerCase())) return false; if (authorFilter && c.author !== authorFilter) return false; if (typeFilter && !(c.message || "").toLowerCase().startsWith(typeFilter.toLowerCase())) return false; return true; }), [commits, search, authorFilter, typeFilter]); + return (
setSearch(e.target.value)} placeholder="Rechercher..." className="w-full pl-9 pr-3 py-2 rounded-md border border-input bg-background text-sm focus:outline-none focus:ring-2 focus:ring-ring" />

{filtered.length} commit{filtered.length > 1 ? "s" : ""}

); +} + +// ==================== Charts ==================== +function DailyBarChart({ data }: { data: { date: string; count: number }[] }) { + const s = [...data].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()).slice(-60); const max = Math.max(...s.map(d => d.count), 1); + if (!s.length) return

Aucune donnée

; + return (
{s.map((d, i) => { const h = Math.max((d.count / max) * 100, 3); return
{i % 15 === 0 && {new Date(d.date).toLocaleDateString("fr-FR", { month: "short", day: "numeric" })}}
; })}
); +} +function HeatmapGrid({ data }: { data: { date: string; count: number; level: number }[] }) { + if (!data?.length) return

Aucune donnée

; + const colors = ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e3a", "#1e6c25"]; const weeks: typeof data[] = []; let cw: typeof data = []; + for (const d of data) { cw.push(d); if (cw.length === 7) { weeks.unshift(cw); cw = []; } } if (cw.length > 0) weeks.unshift(cw); + return (
{weeks.map((w, wi) => (
{w.map((d, di) => (
))}
))}
Moins{colors.map(c =>
)}Plus
); +} +function MonthlyBars({ data }: { data: { dateLabel: string; count: number }[] }) { + const s = [...data].slice(-12); const max = Math.max(...s.map(d => d.count), 1); + if (!s.length) return

Aucune donnée

; + return (
{s.map((d, i) => { const h = Math.max((d.count / max) * 100, 4); return
{d.count || ""}
{d.dateLabel}
; })}
); +} +function ContributorBars({ contributors }: { contributors: { name: string; commits: number }[] }) { + const s = [...contributors].sort((a, b) => b.commits - a.commits).slice(0, 10); const max = Math.max(...s.map(c => c.commits), 1); + if (!s.length) return

Aucune donnée

; + return (
{s.map((c, i) => (
{c.name}{c.commits}
))}
); +} +function CommitTypeBars({ types }: { types: { type: string; percentage: number }[] }) { + const tl: Record = { feat: "Nouveau", fix: "Correction", refactor: "Refactorisation", docs: "Docs", style: "Style", chore: "Tâche", perf: "Perf", test: "Test", ci: "CI/CD", build: "Build", revert: "Revert" }; + const tc: Record = { feat: COLORS[0], fix: COLORS[3], refactor: COLORS[2], docs: COLORS[5], style: COLORS[6], chore: COLORS[9], perf: COLORS[1], test: COLORS[7], ci: COLORS[4], build: COLORS[8], revert: COLORS[9] }; + if (!types.length) return

Aucune donnée

; + return (
{types.filter(t => t.percentage > 0).map(t => (
{tl[t.type] || t.type}{t.percentage}%
))}
); +} +function DonutChart({ types }: { types: { type: string; count: number; percentage: number }[] }) { + const f = types.filter(t => t.count > 0); if (!f.length) return

Aucune donnée

; + const total = f.reduce((s, t) => s + t.count, 0); let cp = 0; + const tl: Record = { feat: "New", fix: "Fix", refactor: "Ref", docs: "Doc", style: "Sty", chore: "Chr", perf: "Prf", test: "Tst", ci: "CI", build: "Bld", revert: "Rvt" }; + return (
{f.map((t, i) => { const p = (t.count / total) * 100; const s = cp; cp += p; const e = cp; const a1 = (s / 100) * 2 * Math.PI, a2 = (e / 100) * 2 * Math.PI; const x1 = 50 + 40 * Math.cos(a1), y1 = 50 + 40 * Math.sin(a1), x2 = 50 + 40 * Math.cos(a2), y2 = 50 + 40 * Math.sin(a2); return 50 ? 1 : 0} 1 ${x2} ${y2} Z`} fill={COLORS[i % COLORS.length]} stroke="var(--background)" strokeWidth="1.5" />; })}{total}
{f.slice(0, 7).map((t, i) => (
{tl[t.type] || t.type}{t.percentage}%
))}
); +} +function StatItem({ label, value }: { label: string; value: string | number }) { + return (

{label}

{typeof value === "number" ? value.toLocaleString("fr-FR") : value}

); +} +function exportCSV(stats: any) { + const lines: string[] = []; const sep = ";"; + lines.push(`GitPulse${sep}${stats.repoName}`, new Date().toLocaleDateString("fr-FR"), "", `Commits${sep}${stats.totalCommits}`, `+Lignes${sep}${stats.totalLinesAdded}`, `-Lignes${sep}${stats.totalLinesDeleted}`, `Jours${sep}${stats.activeDays}`); + for (const c of stats.contributors || []) lines.push(`${c.name}${sep}${c.commits}${sep}${c.linesAdded}${sep}${c.linesDeleted}`); + const blob = new Blob(["\uFEFF" + lines.join("\n")], { type: "text/csv" }); const u = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = u; a.download = `gitpulse-${stats.repoName || "export"}.csv`; a.click(); URL.revokeObjectURL(u); +} diff --git a/src/components/dashboard/active-days-chart.tsx b/src/components/dashboard/active-days-chart.tsx new file mode 100644 index 0000000..9eafeb8 --- /dev/null +++ b/src/components/dashboard/active-days-chart.tsx @@ -0,0 +1,93 @@ +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +interface DailyData { + date: string; + count: number; +} + +interface ActiveDaysProps { + title?: string; + dailyCommits: DailyData[]; + className?: string; +} + +export function ActiveDaysChart({ title, dailyCommits, className }: ActiveDaysProps) { + const sortedData = [...dailyCommits] + .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) + .slice(-30); + + const chartData = sortedData.map((d) => { + const date = new Date(d.date); + const label = `${date.getDate()}/${date.getMonth() + 1}`; + // Calculate 7-day moving average + const idx = sortedData.indexOf(d); + let avg = 0; + if (idx >= 6) { + const slice = sortedData.slice(idx - 6, idx + 1); + avg = parseFloat( + (slice.reduce((sum, c) => sum + c.count, 0) / 6).toFixed(1) + ); + } + return { ...d, label, average: idx >= 6 ? avg : 0 }; + }); + + return ( +
+

{title || "Activité des jours"}

+
+ + + + + + [ + `${value} commits`, + name === "average" ? "Moyenne mobile (7j)" : "Commits", + ]} + /> + + + + +
+
+ ); +} diff --git a/src/components/dashboard/commit-history.tsx b/src/components/dashboard/commit-history.tsx new file mode 100644 index 0000000..d389a1d --- /dev/null +++ b/src/components/dashboard/commit-history.tsx @@ -0,0 +1,385 @@ +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { Progress } from "@/components/ui/progress"; +import type { GitCommit, HeatmapDay } from "@/lib/git-analyzer"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +function getDaysAgo(date: Date): number { + const now = new Date(); + const diff = now.getTime() - date.getTime(); + return Math.floor(diff / (1000 * 60 * 60 * 24)); +} + +interface CommitHistoryProps { + commits: GitCommit[]; + days?: number; + startDate?: Date; +} + +export function CommitHistory({ commits, days = 365 }: CommitHistoryProps) { + const authorMap = new Map(); + for (const c of commits) { + if (!authorMap.has(c.author)) { + authorMap.set(c.author, { count: 0, percentage: 0 }); + } + authorMap.get(c.author)!.count += 1; + } + for (const [author, data] of authorMap) { + data.percentage = commits.length > 0 ? Math.round((data.count / commits.length) * 100) : 0; + } + + return ( + + + Fréquence par contributeur + + +
+ {Array.from(authorMap.entries()).map(([author, data]) => ( +
+
+ {author} + {data.percentage}% +
+ +
+ ))} + {commits.length === 0 && ( +

Aucune donnée

+ )} +
+
+
+ ); +} + +function getHeatmapColor(level: number): string { + const colors = ["#ebedf0", "#c6e48e", "#7bc96f", "#239a3b", "#196127", "#006031"]; + return colors[level] || colors[0]; +} + +export function ContributionHeatmap({ heatmap, days = 365 }: { heatmap: HeatmapDay[]; days?: number }) { + const weeks = Math.ceil(days / 7); + + // Create heatmap grid + const heatmapGrid: { date: string; day: number; count: number; color: string }[][] = []; + + for (let week = 0; week < weeks; week++) { + const weekData: { date: string; day: number; count: number; color: string }[] = []; + for (let day = 0; day < 7; day++) { + const date = new Date(Date.now() - (week * 7 + day) * 24 * 60 * 60 * 1000); + const dateStr = date.toISOString().split("T")[0]; + const found = heatmap.find(h => h.date === dateStr); + const count = found?.count || 0; + const color = getHeatmapColor(count); + + weekData.push({ + date: dateStr, + day: date.getDay(), + count, + color, + }); + } + heatmapGrid.push(weekData); + } + + const dayLabels = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"]; + + return ( + + + Intensité des contributions + + +
+
+ {heatmapGrid[heatmapGrid.length - 1]?.map((row, wIdx) => ( +
+ ))} + {days} jours +
+ +
+ {dayLabels.map((label, idx) => ( +
+ {label} +
+ ))} + {heatmapGrid.map((week, wIdx) => + week.map((cell, dIdx) => ( +
+ )) + )} +
+ +
+ Moins +
+
+
+
+
+ Plus +
+
+ + + ); +} + +function CommitTypeBreakdown({ commitTypes }: { commitTypes: Array<{ type: string; percentage: number }> }) { + const typeColors: Record = { + feat: "bg-emerald-500", + fix: "bg-orange-500", + refactor: "bg-purple-500", + docs: "bg-blue-500", + style: "bg-pink-500", + chore: "bg-gray-500", + perf: "bg-red-500", + test: "bg-yellow-500", + ci: "bg-indigo-500", + build: "bg-cyan-500", + revert: "bg-slate-500", + }; + + const typeLabels: Record = { + feat: 'Nouveau', + fix: 'Correction', + refactor: 'Refactorisation', + docs: 'Documentation', + style: 'Style', + chore: 'Tâche', + perf: 'Performance', + test: 'Test', + ci: 'CI/CD', + build: 'Build', + revert: 'Annulation', + }; + + return ( + + + Répartition des commits + + +
+ {commitTypes.map(({ type, percentage }) => ( +
+
+
+
+ {typeLabels[type] || type} + {percentage}% +
+
+
+
+
+
+ ))} +
+ + + ); +} + +function ContributionBreakdown({ contributors }: { contributors: any[] }) { + return ( + + + Répartition des contributions + + +
+
+
+ + + {contributors.map((_, idx) => { + const colors = ["#0ea5e9", "#f97316", "#8b5cf6", "#10b981", "#6366f1"]; + const total = contributors.reduce((sum, c) => sum + c.commits, 0); + const count = contributors[idx]?.commits || 0; + const percentage = (count / total) * 100; + const dashArray = `${percentage * 0.88} ${100 - percentage * 0.88}`; + const dashOffset = total + ? -contributors.slice(0, idx).reduce((sum, c) => sum + (c.commits / total) * 88, 0) + : 0; + + return ( + + ); + })} + +
+
+

Total

+

{contributors.reduce((sum, c) => sum + c.commits, 0)}

+
+
+
+
+ +
+ {contributors.map((contributor, idx) => { + const total = contributors.reduce((sum, c) => sum + c.commits, 0); + const percentage = total ? Math.round((contributor.commits / total) * 100) : 0; + const colors = ["#0ea5e9", "#f97316", "#8b5cf6", "#10b981", "#6366f1"]; + + return ( +
+
+
+
+ {contributor.name} + {percentage}% +
+
+
+
+
+
+ ); + })} +
+
+ + + ); +} + +function CommitTimeline({ dailyCommits }: { dailyCommits: { date: string; count: number }[] }) { + const maxCount = Math.max(...dailyCommits.map(c => c.count), 1); + const today = new Date(); + const startDate = new Date(today); + startDate.setDate(startDate.getDate() - 365); + + const data = dailyCommits.filter(c => { + const date = new Date(c.date); + return date >= startDate && date <= today; + }); + + return ( + + + Historique des commits + + +
+
+ {data.map((point, idx) => { + const height = Math.max((point.count / maxCount) * 100, 5); + const date = new Date(point.date); + const isWeekend = [0, 6].includes(date.getDay()); + + return ( +
+
+ {idx % 30 === 0 && ( + + {date.toLocaleDateString('fr-FR', { month: 'short' })} + + )} +
+ ); + })} +
+ +
+ {startDate.toLocaleDateString('fr-FR', { month: 'short', day: 'numeric' })} + Aujourd'hui +
+
+ + + ); +} + +function MostActivePeriod({ mostActive, allTime, lastWeek }: { mostActive: Array<{ type: string; count: number }>; allTime: Array<{ type: string; count: number }>; lastWeek: Array<{ type: string; count: number }> }) { + const getMostActive = (period: Array<{ type: string; count: number }>) => { + const max = Math.max(...period.map(p => p.count)); + return period.find(p => p.count === max); + }; + + return ( + + + Périodes les plus actives + + +
+ {[ + { period: 'Aujourd\'hui', data: mostActive }, + { period: 'Cette semaine', data: lastWeek }, + { period: 'Total', data: allTime }, + ].map(({ period, data }) => { + const most = getMostActive(data); + return ( +
+

{period}

+

{most?.count || 0}

+

{most?.type || '-'}

+
+ ); + })} +
+
+
+ ); +} diff --git a/src/components/dashboard/commit-timeline.tsx b/src/components/dashboard/commit-timeline.tsx new file mode 100644 index 0000000..d8f2731 --- /dev/null +++ b/src/components/dashboard/commit-timeline.tsx @@ -0,0 +1,86 @@ +interface CommitInfo { + date: string; + count: number; +} + +interface CommitTimelineProps { + commits: CommitInfo[]; + title?: string; + className?: string; +} + +export function CommitTimeline({ commits, title, className }: CommitTimelineProps) { + const maxValue = Math.max(...commits.map(c => c.count), 1); + const minDate = new Date(Math.min(...commits.map(c => new Date(c.date).getTime()))); + const maxDate = new Date(Math.max(...commits.map(c => new Date(c.date).getTime()))); + + const datesUsed = new Set(commits.map(c => c.date)); + const today = new Date(); + const daysInRange = Math.ceil((today.getTime() - minDate.getTime()) / (1000 * 60 * 60 * 24)); + + // Create the grid of days + const calendarDays: { date: Date; hasCommits?: boolean }[] = []; + const start = new Date(minDate); + start.setDate(start.getDate() - (start.getDay() || 7)); // Start at previous Sunday + + for (let i = 0; i < daysInRange + start.getDay(); i++) { + const date = new Date(start.getTime() + i * 24 * 60 * 60 * 1000); + const dateStr = date.toISOString().split('T')[0]; + calendarDays.push({ + date, + hasCommits: datesUsed.has(dateStr), + }); + } + + const weeks = Math.ceil(calendarDays.length / 7); + const dayLabels = ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']; + + return ( +
+

{title || "Historique des commits"}

+
+
+ {Array.from({ length: 7 }).map((_, i) => ( +
+ {dayLabels[i]} +
+ ))} +
+
+ {Array.from({ length: weeks }).map((_, weekIndex) => ( +
+ {Array.from({ length: 7 }).map((_, dayIndex) => { + const cellIndex = weekIndex * 7 + dayIndex; + const cell = calendarDays[cellIndex]; + + if (!cell) return
; + + return ( +
c.date === cell.date.toISOString().split("T")[0])?.count || 0 : 0} commits`} + /> + ); + })} +
+ ))} +
+
+ +
+ Moins +
+
+
+
+
+ Plus +
+
+ ); +} diff --git a/src/components/dashboard/contributor-chart.tsx b/src/components/dashboard/contributor-chart.tsx new file mode 100644 index 0000000..b66ae84 --- /dev/null +++ b/src/components/dashboard/contributor-chart.tsx @@ -0,0 +1,80 @@ +interface Contributor { + name: string; + email: string; + commits: number; + linesAdded: number; + linesDeleted: number; +} + +interface ContributorChartProps { + contributors: Contributor[]; + title?: string; + className?: string; +} + +export function ContributorChart({ contributors, title, className }: ContributorChartProps) { + const maxValue = Math.max(...contributors.map((c) => c.commits), 1); + const barColors = [ + '#0ea5e9', + '#f97316', + '#8b5cf6', + '#10b981', + '#6366f1', + '#ec4899', + '#14b8a6', + '#f59e0b', + '#64748b', + ]; + + const sorted = [...contributors].sort((a, b) => b.commits - a.commits).slice(0, 10); + + return ( +
+

{title || "Top Contributeurs"}

+ {sorted.map((contributor, index) => { + const percentage = (contributor.commits / maxValue) * 100; + const color = barColors[index % barColors.length]; + + return ( +
+
+
+
+ {contributor.name} +
+ + {contributor.commits} commits + +
+
+
+
+
+ ); + })} + {sorted.length === 0 && ( +

+ Aucune donnée de contributeur +

+ )} +
+ ); +} + +// Add TypeScript types for props +export interface ContributorData { + name: string; + email: string; + commits: number; + linesAdded: number; + linesDeleted: number; +} diff --git a/src/components/dashboard/git-commit-stats.tsx b/src/components/dashboard/git-commit-stats.tsx new file mode 100644 index 0000000..b71fe12 --- /dev/null +++ b/src/components/dashboard/git-commit-stats.tsx @@ -0,0 +1,41 @@ +interface CommitsByDateProps { + commits: Array<{ date: string; count: number }>; + className?: string; + } + + export function CommitsByDateChart({ commits, className }: CommitsByDateProps) { + const sortedCommits = [...commits].sort( + (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() + ); + + const maxValue = Math.max(...sortedCommits.map(c => c.count), 1); + + return ( +
+

Commits par date

+
+ {sortedCommits.map((commit, idx) => { + const height = (commit.count / maxValue) * 100; + const date = new Date(commit.date); + const label = date.toLocaleDateString("fr-FR", { day: "2-digit", month: "short" }); + return ( +
+ + {commit.count > 0 ? commit.count : ''} + +
0 ? '24px' : '0' + }} + title={`${label}: ${commit.count} commits`} + /> + {label} +
+ ); + })} +
+
+ ); + } diff --git a/src/components/dashboard/git-diff.tsx b/src/components/dashboard/git-diff.tsx new file mode 100644 index 0000000..9320fb1 --- /dev/null +++ b/src/components/dashboard/git-diff.tsx @@ -0,0 +1,271 @@ +type CommitsByDate = { date: string; count: number }; +type CommitsByDayOfWeek = { day: number; count: number }; +type CommitsByMonth = { month: number; count: number }; +type CommitsByHourOfDay = { hour: number; count: number }; +type FilesByType = { type: string; count: number }; + +function formatDateFr(dateStr: string): string { + const d = new Date(dateStr); + return d.toLocaleDateString("fr-FR", { day: "numeric", month: "short" }); +} + +function formatHourFr(hour: number): string { + return `${hour.toString().padStart(2, "0")}h00`; +} + + interface CommitsByDateProps { + commits: CommitsByDate[]; + className?: string; + } + + export function CommitsByDateChart({ commits, className }: CommitsByDateProps) { + const sortedCommits = [...commits].sort( + (a, b) => + new Date(a.date).getTime() - new Date(b.date).getTime() + ); + + const maxValue = Math.max(...sortedCommits.map(c => c.count), 1); + const labels = sortedCommits.map((c) => formatDateFr(c.date)); + + return { commits }; + } + + interface CommitsByDayOfWeekProps { + commits: CommitsByDayOfWeek[]; + className?: string; + } + + export function CommitsByDayOfWeekChart({ commits, className }: CommitsByDayOfWeekProps) { + const maxValue = Math.max(...commits.map(p => p.count), 1); + const days = [ + 'Dimanche', + 'Lundi', + 'Mardi', + 'Mercredi', + 'Jeudi', + 'Vendredi', + 'Samedi' + ]; + + const data = days.map((day, idx) => { + const found = commits.find(p => p.day === idx); + return { + day: day, + count: found ? found.count : 0, + percentage: found ? Math.round((found.count / maxValue) * 100) : 0 + }; + }).sort((a, b) => b.count - a.count); + + return ( +
+

Jours de la semaine

+
+ {data.map((item, idx) => ( +
+
+ {item.day.substring(0, 3)} +
+
+
+ {item.count} +
+ ))} +
+
+ ); + } + + function getDayColor(idx: number) { + const colors = ['#8b5cf6', '#3b82f6', '#06b6d4', '#10b981', '#f59e0b', '#f97316', '#ef4444']; + return colors[idx]; + } + + interface CommitsByMonthProps { + commits: CommitsByMonth[]; + className?: string; + } + + export function CommitsByMonthChart({ commits, className }: CommitsByMonthProps) { + const months = [ + 'Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Juin', + 'Juil', 'Aou', 'Sep', 'Oct', 'Nov', 'Dec' + ]; + + const data = months.map((month, idx) => { + const found = commits.find(p => p.month === idx); + return { + month, + count: found ? found.count : 0 + }; + }); + + return ( +
+

Mois de l'année

+
+ {data.map((item, idx) => { + const maxValue = Math.max(...data.map(d => d.count), 1); + const height = (item.count / maxValue) * 100; + return ( +
+ + {item.count > 0 ? item.count : ''} + +
0 ? '24px' : '0' }} + /> + {item.month} +
+ ); + })} +
+
+ ); + } + + interface CommitsByHourOfDayProps { + commits: CommitsByHourOfDay[]; + className?: string; + } + + export function CommitsByHourOfDayChart({ commits, className }: CommitsByHourOfDayProps) { + const maxValue = Math.max(...commits.map(p => p.count), 1); + const hours = Array.from({ length: 24 }, (_, i) => { + const found = commits.find(p => p.hour === i); + return { + hour: i, + label: formatHourFr(i), + count: found ? found.count : 0, + percentage: found ? (found.count / maxValue) * 100 : 0 + }; + }); + + return ( +
+

Heures de la journée

+
+ {hours.map((item) => ( +
+
0 ? Math.max(item.percentage * 0.8, 5) : 0}%` }} + /> +
+ ))} +
+
+ 00h00 + 12h00 + 23h59 +
+
+ ); + } + + interface FilesByTypeProps { + files: FilesByType[]; + className?: string; + } + + export function FilesByTypeChart({ files, className }: FilesByTypeProps) { + const colors = { + src: '#3b82f6', + css: '#8b5cf6', + html: '#f59e0b', + json: '#64748b', + md: '#10b981', + ts: '#06b6d4', + test: '#ec4899', + config: '#92400e', + git: '#6b7280', + asset: '#f97316', + other: '#a3a3a3', + }; + + const sorted = [...files].sort((a, b) => b.count - a.count); + const maxCount = sorted[0]?.count || 1; + + return ( +
+

Types de fichiers modifiés

+
+ {sorted.map((file, idx) => { + const percentage = (file.count / maxCount) * 100; + const ext = file.type.toLowerCase(); + const color = colors[ext as keyof typeof colors] || '#a3a3a3'; + + return ( +
+
+ .{ext} +
+
+
+ {file.count} +
+ ); + })} +
+
+ ); + } + + interface ActiveDaysProps { + activeDays: number; + totalDays: number; + className?: string; + } + + export function ActiveDaysChart({ activeDays, totalDays, className }: ActiveDaysProps) { + const percentage = totalDays > 0 ? Math.round((activeDays / totalDays) * 100) : 0; + const color = + percentage > 60 ? 'text-green-500' : + percentage > 30 ? 'text-yellow-500' : + 'text-red-500'; + + return ( +
+

Jours actifs

+
+
+
{activeDays}
+
jours actifs
+
+
+
+ {percentage}% des {totalDays} jours +
+
+
60 ? 'bg-green-500' : + percentage > 30 ? 'bg-yellow-500' : + 'bg-red-500' + }`} + style={{ width: `${percentage}%` }} + /> +
+
+ taux d'activité +
+
+
+
+ ); + } diff --git a/src/components/dashboard/index.ts b/src/components/dashboard/index.ts new file mode 100644 index 0000000..7583e93 --- /dev/null +++ b/src/components/dashboard/index.ts @@ -0,0 +1,7 @@ +export { RepoManager, StatsOverview, CommitHistory } from "./repo-manager"; +export { ActiveDaysChart } from "./active-days-chart"; +export { CommitTimeline } from "./commit-timeline"; +export { ContributorChart } from "./contributor-chart"; +export { CommitsByDateChart } from "./git-commit-stats"; +export { CommitsByDayOfWeekChart, CommitsByMonthChart, CommitsByHourOfDayChart, FilesByTypeChart } from "./git-diff"; +export { ContributionHeatmap } from "./commit-history"; diff --git a/src/components/dashboard/repo-manager.tsx b/src/components/dashboard/repo-manager.tsx new file mode 100644 index 0000000..95f4f39 --- /dev/null +++ b/src/components/dashboard/repo-manager.tsx @@ -0,0 +1,164 @@ +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Select } from "@/components/ui/select"; +import { Plus, X, Folder, FolderOpen, Search, Trash2, Clock, GitGraph, GitBranch, FileText } from "lucide-react"; +import Image from "next/image"; + +export function RepoManager({ + repos, + onAdd, + onRemove, +}: { + repos: { path: string; name: string }[]; + onAdd: (repo: { path: string; name: string }) => void; + onRemove: (path: string) => void; +}) { + return ( + +

+ + Dépôts Git +

+
+ {repos.map((repo) => ( +
+
+ +
+

{repo.name}

+

{repo.path}

+
+
+ +
+ ))} + +
+
+ ); +} + +export function StatsOverview({ stats }: { stats: any }) { + return ( +
+ } + color="blue" + /> + } + color="green" + /> + } + color="red" + /> + } + color="yellow" + /> +
+ ); +} + +function StatCard({ + title, + value, + icon, + color, + format, +}: { + title: string; + value: number | string; + icon: React.ReactNode; + color: string; + format: "number" | "percent"; +}) { + const colorMap: Record = { + blue: "bg-blue-50 text-blue-600 border-blue-200", + green: "bg-green-50 text-green-600 border-green-200", + red: "bg-red-50 text-red-600 border-red-200", + yellow: "bg-yellow-50 text-yellow-600 border-yellow-200", + }; + + const displayed = format === "number" + ? Number(value).toLocaleString("fr-FR") + : `${value}%`; + + return ( + +
+
+

{title}

+

{displayed}

+
+
+ {icon} +
+
+
+ ); +} + +export function CommitHistory({ commits }: { commits: any[] }) { + return ( +
+

+ + Historique des commits (derniers 10) +

+
+ {commits.slice(0, 10).map((commit, idx) => ( +
+
+
+

{commit.msg}

+
+ {commit.author} + + {commit.date} + + +{commit.add} -{commit.del} +
+
+ + {commit.hash.substring(0, 7)} + +
+ ))} +
+
+ ); +} diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx new file mode 100644 index 0000000..ae758f4 --- /dev/null +++ b/src/components/ui/accordion.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Accordion = ({ + children, + className, +}: React.ComponentProps<"div">) => { + return
{children}
; +}; + +const AccordionItem = ({ + children, + className, +}: React.ComponentProps<"div">) => { + return
{children}
; +}; + +const AccordionTrigger = ({ + children, + className, + onClick, +}: React.ComponentProps<"button">) => { + return ( + + ); +}; + +const AccordionContent = ({ + children, + className, +}: React.ComponentProps<"div">) => { + return ( +
{children}
+ ); +}; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..67997dd --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,34 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & { variant?: "default" | "destructive" } +>(({ className, variant = "default", ...props }, ref) => ( +
svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + variant === "destructive" && + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + className + )} + {...props} + /> +)); +Alert.displayName = "Alert"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertDescription }; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..9d1ba87 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +"use client"; + +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..e62dbcf --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,78 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..b8a3911 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + } +); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 0000000..299e418 --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Progress = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & { value: number } +>(({ className, value = 0, ...props }, ref) => ( +
+
+
+)); +Progress.displayName = "Progress"; + +export { Progress }; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..ecd6424 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +export interface SelectProps + extends React.SelectHTMLAttributes {} + +const Select = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( + + ); + } +); +Select.displayName = "Select"; + +export { Select }; diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..da702d8 --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,16 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ); +} + +export { Skeleton }; diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..24e5839 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,80 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = "TableCell"; + +export { Table, TableHeader, TableRow, TableHead, TableBody, TableCell }; diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..006e2da --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,95 @@ +"use client"; + +import * as React from "react"; +import { cn } from "@/lib/utils"; + +interface TabsContextType { + value: string; + onValueChange: (value: string) => void; +} + +const TabsContext = React.createContext(null); + +function useTabs() { + const ctx = React.useContext(TabsContext); + if (!ctx) throw new Error("Tabs components must be used within "); + return ctx; +} + +function Tabs({ + defaultValue, + className, + children, +}: { + defaultValue: string; + className?: string; + children: React.ReactNode; +}) { + const [value, setValue] = React.useState(defaultValue); + return ( + +
{children}
+
+ ); +} + +function TabsList({ + className, + children, +}: { + className?: string; + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} + +function TabsTrigger({ + value, + className, + children, +}: { + value: string; + className?: string; + children: React.ReactNode; +}) { + const { value: activeValue, onValueChange } = useTabs(); + const isActive = value === activeValue; + return ( + + ); +} + +function TabsContent({ + value, + className, + children, +}: { + value: string; + className?: string; + children: React.ReactNode; +}) { + const { value: activeValue } = useTabs(); + if (value !== activeValue) return null; + return
{children}
; +} + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..3b7f073 --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as React from "react"; +import { cn } from "@/lib/utils"; + +interface TooltipProps { + content: string; + children: React.ReactNode; + className?: string; +} + +export function Tooltip({ content, children, className }: TooltipProps) { + const [show, setShow] = React.useState(false); + + return ( +
setShow(true)} + onMouseLeave={() => setShow(false)} + > + {children} + {show && ( +
+ {content} +
+
+ )} +
+ ); +} diff --git a/src/lib/git-analyzer.ts b/src/lib/git-analyzer.ts new file mode 100644 index 0000000..e506c90 --- /dev/null +++ b/src/lib/git-analyzer.ts @@ -0,0 +1,666 @@ +import { simpleGit } from "simple-git"; +import OpenAI from "openai"; + +const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL || "http://localhost:11434/v1"; +const OLLAMA_API_URL = "http://localhost:11434/api"; + +export async function detectOllamaModel(): Promise { + // Priority: env var > auto-detect > null + if (process.env.OLLAMA_MODEL) return process.env.OLLAMA_MODEL; + try { + const res = await fetch(`${OLLAMA_API_URL}/tags`, { signal: AbortSignal.timeout(3000) }); + if (!res.ok) return null; + const data = await res.json(); + const models: string[] = (data.models || []).map((m: any) => m.name); + if (models.length === 0) return null; + // Prefer smaller/faster models first + const preferred = ["gemma4", "gemma3", "gemma", "llama3.2", "llama3", "phi", "qwen", "mistral"]; + for (const p of preferred) { + const match = models.find((m) => m.includes(p)); + if (match) return match; + } + return models[0]; + } catch { + return null; + } +} + +export interface GitCommit { + hash: string; + shortHash: string; + author: string; + email: string; + date: Date; + message: string; + files: number; + insertions: number; + deletions: number; + filesList: { path: string; additions: number; deletions: number }[]; +} + +export interface RepoStats { + repoPath: string; + repoName: string; + totalCommits: number; + totalLinesAdded: number; + totalLinesDeleted: number; + totalFilesChanged: number; + activeDays: number; + contributors: AuthorStats[]; + topContributor: AuthorStats; + mostActivePeriod: { type: string; count: number }; + commitTypes: CommitTypeStats[]; + timeSeries: TimeSeriesData; + heatmap: HeatmapDay[]; + fileChanges: FileChangeStats[]; + summary: string; + recentCommits: GitCommit[]; +} + +export interface AuthorStats { + name: string; + email: string; + commits: number; + linesAdded: number; + linesDeleted: number; + daysActive: number; +} + +export interface CommitTypeStats { + type: string; + count: number; + percentage: number; +} + +export interface TimeSeriesData { + daily: TimePoint[]; + weekly: TimePoint[]; + monthly: TimePoint[]; +} + +export interface TimePoint { + date: string; + dateLabel: string; + count: number; + linesAdded: number; + linesDeleted: number; +} + +export interface HeatmapDay { + date: string; + day: number; + count: number; + level: number; +} + +export interface FileChangeStats { + path: string; + additions: number; + deletions: number; + totalChanges: number; + commits: number; +} + +export interface ComparisonData { + repos: RepoStats[]; + labels: string[]; + commitComparison: { repo: string; commits: number; linesAdded: number; linesDeleted: number }[]; + contributorComparison: { name: string; repos: string[] }[]; +} + +const KNOWN_COMMIT_TYPES = ["feat", "fix", "refactor", "docs", "style", "chore", "perf", "test", "ci", "build", "revert"] as const; +type KnownCommitType = (typeof KNOWN_COMMIT_TYPES)[number]; + +const COMMIT_TYPE_LABELS: Record = { + feat: "Nouveau", + fix: "Correction", + refactor: "Refactorisation", + docs: "Documentation", + style: "Style", + chore: "Tâche", + perf: "Performance", + test: "Test", + ci: "CI/CD", + build: "Build", + revert: "Annulation", +}; + +const DAYS_FR = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"]; +const MONTHS_FR = ["Jan", "Fév", "Mar", "Avr", "Mai", "Jun", "Jul", "Aoû", "Sep", "Oct", "Nov", "Déc"]; + +function analyzeCommitType(msg: string): KnownCommitType { + const lower = msg.toLowerCase().trim(); + for (const type of KNOWN_COMMIT_TYPES) { + if (lower.startsWith(`${type}:`) || lower.startsWith(`${type}(`) || lower.startsWith(`[${type}]`)) { + return type; + } + } + for (const type of KNOWN_COMMIT_TYPES) { + if (lower.includes(`${type}:`) || lower.includes(`${type} `)) { + return type; + } + } + return "chore"; +} + +export function getHeatmapLevel(count: number): number { + if (count === 0) return 0; + if (count <= 3) return 1; + if (count <= 6) return 2; + if (count <= 10) return 3; + if (count <= 20) return 4; + return 5; +} + +export function formatHeatmapColor(level: number): string { + const colors = ["#ebedf0", "#c6e48e", "#7bc96f", "#239a3b", "#196127", "#006031"]; + return colors[level] || colors[0]; +} + +export function getContributionsColor(level: number): string { + const colors = ["hsl(var(--muted))", "#9BE9A8", "#40C463", "#30A14E", "#216E3A", "#1E6C25"]; + return colors[level] || colors[0]; +} + +export function formatDateFr(date: Date): string { + const d = date.getDate(); + const m = MONTHS_FR[date.getMonth()]; + const y = date.getFullYear(); + return `${d} ${m} ${y}`; +} + +export function formatShortDateFr(date: Date): string { + const d = date.getDate(); + const m = MONTHS_FR[date.getMonth()]; + return `${d} ${m}`; +} + +export function formatNumber(n: number): string { + if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`; + if (n >= 1000) return `${(n / 1000).toFixed(1)}K`; + return n.toString(); +} + +export function getDaysAgo(date: Date): number { + const now = new Date(); + const diff = now.getTime() - date.getTime(); + return Math.floor(diff / (1000 * 60 * 60 * 24)); +} + +export function getRelevantDates( + commits: GitCommit[], + days: number = 365, + startDate?: Date +) { + const start = startDate + ? new Date(startDate) + : new Date(); + start.setDate(start.getDate() - days); + start.setHours(0, 0, 0, 0); + + return commits.filter((c) => c.date >= start); +} + +export async function analyzeGitRepo( + repoPath: string, + branch: string = "main", + days: number = 365, + options: { apiKey?: string; maxCommits?: number; skipAI?: boolean } = {} +): Promise { + const git = simpleGit(repoPath); + + await git.checkIsRepo(); + + const repo = await simpleGit(repoPath).revparse(["--show-toplevel"]); + const repoName = Array.isArray(repo) ? repo[repo.length - 1] : repo; + + // Use git raw with --numstat to get per-file additions/deletions + const maxCount = options.maxCommits ?? 5000; + const gitLogRaw = await git.raw([ + "log", + "--numstat", + "-n", String(maxCount), + "--format=%H|||%h|||%an|||%ae|||%aI|||%s", + ]); + + // Parse: each commit block starts with hash|||... followed by numstat file lines + const relevantCommits: GitCommit[] = []; + const blocks = gitLogRaw.trim().split(/\n(?=[0-9a-f]{40}\|\|\|)/); + + for (const block of blocks) { + const lines = block.trim().split("\n"); + const header = lines[0]; + const parts = header.split("|||"); + if (parts.length < 6) continue; + + const hash = parts[0]; + const shortHash = parts[1]; + const author = parts[2]; + const email = parts[3]; + const date = new Date(parts[4]); + const message = parts[5] || ""; + + // Parse numstat lines: "additions\tsuppressions\tpath" + let totalAdditions = 0; + let totalDeletions = 0; + const filesList: { path: string; additions: number; deletions: number }[] = []; + + for (let i = 1; i < lines.length; i++) { + const line = lines[i].trim(); + if (!line) continue; + // numstat format: "additions\tdeletions\tpath" (additions/deletions can be "-" for binary) + const m = line.match(/^(\d+|-)\t(\d+|-)\t(.+)$/); + if (m) { + const adds = m[1] === "-" ? 0 : parseInt(m[1]); + const dels = m[2] === "-" ? 0 : parseInt(m[2]); + const path = m[3]; + totalAdditions += adds; + totalDeletions += dels; + filesList.push({ path, additions: adds, deletions: dels }); + } + } + + const filesChanged = filesList.length; + + // Apply date filter if needed + if (days < 365) { + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - days); + if (date < cutoff) continue; + } + + relevantCommits.push({ + hash, + shortHash, + author, + email, + date, + message, + files: filesChanged, + insertions: totalAdditions, + deletions: totalDeletions, + filesList, + }); + } + + const totalCommits = relevantCommits.length; + + const totalLinesAdded = relevantCommits.reduce((s, c) => s + c.insertions, 0); + const totalLinesDeleted = relevantCommits.reduce((s, c) => s + c.deletions, 0); + const totalFilesChanged = relevantCommits.reduce((s, c) => s + c.files, 0); + + const activeDaysSet = new Set(relevantCommits.map((c) => c.date.toDateString())); + const activeDays = activeDaysSet.size; + + const authorMap = new Map(); + const authorDays = new Map>(); + for (const c of relevantCommits) { + const key = `${c.author}<${c.email}>`; + if (!authorMap.has(key)) { + authorMap.set(key, { name: c.author, email: c.email, commits: 0, linesAdded: 0, linesDeleted: 0, daysActive: 0 }); + } + const authorStat = authorMap.get(key)!; + authorStat.commits += 1; + authorStat.linesAdded += c.insertions; + authorStat.linesDeleted += c.deletions; + + if (!authorDays.has(key)) authorDays.set(key, new Set()); + authorDays.get(key)!.add(c.date.toDateString()); + } + + for (const [key, days] of authorDays) { + authorMap.get(key)!.daysActive = days.size; + } + + const contributors = Array.from(authorMap.values()) + .sort((a, b) => b.commits - a.commits); + + const topContributor = contributors[0] || { name: "Aucun", email: "", commits: 0, linesAdded: 0, linesDeleted: 0, daysActive: 0 }; + + const today = new Date(); + today.setHours(23, 59, 59, 999); + const dayStart = new Date(today); + dayStart.setHours(0, 0, 0, 0); + const dayEnd = new Date(today); + dayEnd.setHours(23, 59, 59, 999); + + const weekStart = new Date(today); + weekStart.setDate(weekStart.getDate() - weekStart.getDay()); + weekStart.setHours(0, 0, 0, 0); + const weekEnd = new Date(weekStart); + weekEnd.setDate(weekEnd.getDate() + 6); + weekEnd.setHours(23, 59, 59, 999); + + const monthStart = new Date(today.getFullYear(), today.getMonth(), 1); + monthStart.setHours(0, 0, 0, 0); + const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0, 23, 59, 59, 999); + + const commitByDay: Record = {}; + const commitByWeek: Record = {}; + const commitByMonth: Record = {}; + + for (const c of relevantCommits) { + const ds = c.date.toISOString().split("T")[0]; + if (!commitByDay[ds]) commitByDay[ds] = []; + commitByDay[ds].push(c); + + const ws = weekStart.toISOString(); + if (c.date >= weekStart && c.date <= weekEnd) { + const wkIdx = Math.floor((c.date.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000)); + const wkKey = `${weekStart.getFullYear()}-S${String(wkIdx + 1).padStart(2, "0")}`; + if (!commitByWeek[wkKey]) commitByWeek[wkKey] = []; + commitByWeek[wkKey].push(c); + } + + const ms = `${c.date.getFullYear()}-${String(c.date.getMonth() + 1).padStart(2, "0")}`; + if (!commitByMonth[ms]) commitByMonth[ms] = []; + commitByMonth[ms].push(c); + } + + const todayDayCount = commitByDay[dayStart.toISOString().split("T")[0]]?.length ?? 0; + const weekDayCount = commitByWeek[`${weekStart.getFullYear()}-S${String(Math.floor((today.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000) + 1)).padStart(2, "0")}`]?.length ?? 0; + const monthDayCount = commitByMonth[`${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}`]?.length ?? 0; + + let mostActivePeriod = { type: "Aujourd'hui", count: todayDayCount }; + if (weekDayCount > mostActivePeriod.count) mostActivePeriod = { type: "Cette semaine", count: weekDayCount }; + if (monthDayCount > mostActivePeriod.count) mostActivePeriod = { type: "Ce mois", count: monthDayCount }; + + const daily: TimePoint[] = []; + const now = Date.now(); + for (let i = 0; i < days; i++) { + const date = new Date(now - i * 24 * 60 * 60 * 1000); + const key = date.toISOString().split("T")[0]; + const count = commitByDay[key]?.length ?? 0; + const ins = commitByDay[key]?.reduce((s, c) => s + c.insertions, 0) ?? 0; + const dels = commitByDay[key]?.reduce((s, c) => s + c.deletions, 0) ?? 0; + daily.push({ + date: key, + dateLabel: formatShortDateFr(date), + count, + linesAdded: ins, + linesDeleted: dels, + }); + } + + for (let i = 0; i < 20; i++) { + const weekStartCalc = new Date(now - i * 7 * 24 * 60 * 60 * 1000); + weekStartCalc.setHours(0, 0, 0, 0); + const weekEndCalc = new Date(weekStartCalc); + weekEndCalc.setDate(weekEndCalc.getDate() + 6); + weekEndCalc.setHours(23, 59, 59, 999); + const weekCommits = relevantCommits.filter((c) => c.date >= weekStartCalc && c.date <= weekEndCalc); + if (weekCommits.length > 0) { + const wkIdx = i + 1; + const wkKey = `${weekStartCalc.getFullYear()}-S${String(Math.ceil((weekStartCalc.getTime() - new Date(weekStartCalc.getFullYear(), 0, 1).getTime()) / (7 * 24 * 60 * 60 * 1000) + 1)).padStart(2, "0")}`; + daily.push({ + date: weekStartCalc.toISOString().split("T")[0], + dateLabel: `S${wkIdx} (${formatShortDateFr(weekStartCalc)})`, + count: weekCommits.length, + linesAdded: weekCommits.reduce((s, c) => s + c.insertions, 0), + linesDeleted: weekCommits.reduce((s, c) => s + c.deletions, 0), + }); + } + } + + const weekly = Object.entries(commitByWeek) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([label, commits]) => ({ + date: label, + dateLabel: label, + count: commits.length, + linesAdded: commits.reduce((s, c) => s + c.insertions, 0), + linesDeleted: commits.reduce((s, c) => s + c.deletions, 0), + })); + + const monthly = Object.entries(commitByMonth) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([label, commits]) => { + const [year, monthStr] = label.split("-"); + const monthNum = parseInt(monthStr) - 1; + return { + date: label, + dateLabel: `${MONTHS_FR[monthNum]} ${year}`, + count: commits.length, + linesAdded: commits.reduce((s, c) => s + c.insertions, 0), + linesDeleted: commits.reduce((s, c) => s + c.deletions, 0), + }; + }); + + const commitTypeMap = new Map(); + for (const type of KNOWN_COMMIT_TYPES) commitTypeMap.set(type, 0); + for (const c of relevantCommits) { + const t = analyzeCommitType(c.message); + commitTypeMap.set(t, (commitTypeMap.get(t) || 0) + 1); + } + + const commitTypes: CommitTypeStats[] = []; + for (const [type, count] of commitTypeMap.entries()) { + commitTypes.push({ + type, + count, + percentage: totalCommits > 0 ? Math.round((count / totalCommits) * 100) : 0, + }); + } + commitTypes.sort((a, b) => b.count - a.count); + + const heatmap: HeatmapDay[] = []; + for (let i = 0; i < days; i++) { + const date = new Date(now - i * 24 * 60 * 60 * 1000); + const ds = date.toISOString().split("T")[0]; + const dayCommits = commitByDay[ds] || []; + const count = dayCommits.length; + const level = getHeatmapLevel(count); + + heatmap.push({ + date: ds, + day: date.getDay(), + count, + level, + }); + } + + const fileMap = new Map }>(); + for (const c of relevantCommits) { + for (const f of c.filesList) { + const key = f.path; + if (!fileMap.has(key)) fileMap.set(key, { additions: 0, deletions: 0, commits: new Set() }); + const entry = fileMap.get(key)!; + entry.additions += f.additions; + entry.deletions += f.deletions; + entry.commits.add(c.hash); + } + } + + const fileChanges: FileChangeStats[] = Array.from(fileMap.entries()) + .map(([path, data]) => ({ + path, + additions: data.additions, + deletions: data.deletions, + totalChanges: data.additions + data.deletions, + commits: data.commits.size, + })) + .sort((a, b) => b.totalChanges - a.totalChanges) + .slice(0, 50); + + let summary = ""; + + const daysRange = days; + const avgCommitsPerDay = totalCommits / Math.max(daysRange, 1); + const avgFilesPerCommit = totalFilesChanged / Math.max(totalCommits, 1); + + summary += `Le projet "${repoName}" présente ${totalCommits} commits sur les derniers ${daysRange} jours.\n\n`; + summary += `- ${contributors.length} contributeur(s) actif(s)\n`; + summary += `- ${totalLinesAdded} lignes ajoutées (+), ${totalLinesDeleted} lignes supprimées (-)\n`; + summary += `- ${totalFilesChanged} fichiers modifiés\n`; + summary += `- ${activeDays} jours actifs\n`; + summary += `- Moyenne : ${avgCommitsPerDay.toFixed(1)} commits/jour\n`; + summary += `- ${contributors.length > 0 ? topContributor.name : "N/A"} est le contributeur principal\n\n`; + + const topType = commitTypes[0]; + if (topType) { + summary += `Type de commit le plus fréquent : "${COMMIT_TYPE_LABELS[topType.type as KnownCommitType] || topType.type}" (${topType.count}, ${topType.percentage}%)\n`; + } + + const maxActiveWeeks: Array<{ week: number; count: number }> = []; + for (let w = 0; w < 52; w++) { + const wkStart = new Date(now - w * 7 * 24 * 60 * 60 * 1000); + wkStart.setHours(0, 0, 0, 0); + const wkEnd = new Date(wkStart); + wkEnd.setDate(wkEnd.getDate() + 6); + wkEnd.setHours(23, 59, 59, 999); + const count = relevantCommits.filter((c) => c.date >= wkStart && c.date <= wkEnd).length; + if (count > 0) maxActiveWeeks.push({ week: w + 1, count }); + } + const topWeek = maxActiveWeeks.sort((a, b) => b.count - a.count)[0]; + if (topWeek) { + summary += `Semaine la plus active : S${String(topWeek.week).padStart(2, "0")} (${topWeek.count} commits)\n`; + } + + if (avgFilesPerCommit > 3) { + summary += `Les commits sont larges en moyenne (${avgFilesPerCommit.toFixed(1)} fichiers/commit). Il pourrait être utile de découper les modifications.\n`; + } + + if (contributors.length > 1) { + const topContribs = contributors.slice(0, 3).map((c) => c.name).join(", "); + summary += `Top contributeurs : ${topContribs}\n`; + } + + if (totalCommits === 0) { + summary += "Aucun commit trouvé dans la période sélectionnée."; + } + + return { + repoPath, + repoName: repoName.replace(/\/$/, ""), + totalCommits, + totalLinesAdded, + totalLinesDeleted, + totalFilesChanged, + activeDays, + contributors, + topContributor, + mostActivePeriod, + commitTypes, + timeSeries: { daily: daily.reverse(), weekly, monthly }, + heatmap, + fileChanges, + summary, + recentCommits: relevantCommits.slice(0, 200), + }; +} + +export function compareRepos( + repos: Array<{ path: string; name: string }>, + days = 365, + options = {} +): Promise { + return Promise.all( + repos.map((repo) => + analyzeGitRepo(repo.path, undefined, days, options).catch(() => null) + ) + ).then((results): ComparisonData => { + const validRepos = results.filter((r) => r !== null) as RepoStats[]; + return { + repos: validRepos, + labels: repos.map((r) => r.name), + commitComparison: validRepos.map((r) => ({ + repo: r.repoName, + commits: r.totalCommits, + linesAdded: r.totalLinesAdded, + linesDeleted: r.totalLinesDeleted, + })), + contributorComparison: [], + }; + }); +} + +export function exportToCSV(stats: RepoStats, filename: string = "git-stats"): string { + const lines: string[] = []; + const sep = ";"; + + const pushHeader = (headers: string[]) => lines.push(headers.join(sep)); + const pushRow = (row: string[]) => lines.push(row.join(sep)); + + lines.push(`Statistiques Git${sep}${stats.repoName.toUpperCase()}`); + lines.push(`Date${sep}${new Date().toLocaleDateString("fr-FR")}`); + lines.push(``); + + lines.push("INDICATEUR VALEUR"); + lines.push(`Total commits${sep}${stats.totalCommits}`); + lines.push(`Lignes ajoutées${sep}${stats.totalLinesAdded}`); + lines.push(`Lignes supprimées${sep}${stats.totalLinesDeleted}`); + lines.push(`Fichiers modifiés${sep}${stats.totalFilesChanged}`); + lines.push(`Jours actifs${sep}${stats.activeDays}`); + lines.push(``); + + lines.push("CONTRIBUTEURS"); + pushHeader(["Nom", "Email", "Commits", "Lignes ajoutées", "Lignes supprimées", "Jours actifs"]); + for (const c of stats.contributors) { + pushRow([c.name, c.email, String(c.commits), String(c.linesAdded), String(c.linesDeleted), String(c.daysActive)]); + } + lines.push(``); + + lines.push("TYPES DE COMMITS"); + pushHeader(["Type", "Nom français", "Nombre", "Pourcentage"]); + for (const t of stats.commitTypes) { + pushRow([t.type, COMMIT_TYPE_LABELS[t.type as KnownCommitType] || t.type, String(t.count), `${t.percentage}%`]); + } + lines.push(``); + + lines.push("MODIFICATIONS DE FICHIERS"); + pushHeader(["Fichier", "Ajouts", "Suppressions", "Total", "Commits"]); + for (const f of stats.fileChanges.slice(0, 30)) { + pushRow([f.path, String(f.additions), String(f.deletions), String(f.totalChanges), String(f.commits)]); + } + + const content = lines.join("\n"); + const bom = "\uFEFF"; + return bom + content; +} + +// ==================== AI Summary (async, separate from analysis) ==================== + +export async function generateAISummary(stats: RepoStats, model?: string): Promise { + const selectedModel = model || (await detectOllamaModel()); + if (!selectedModel) return "Aucun modèle Ollama disponible. Lancez `ollama pull llama3.2`."; + + try { + const openai = new OpenAI({ baseURL: OLLAMA_BASE_URL, apiKey: "ollama" }); + + // Build context from recent commits + const commitSamples = (stats.recentCommits || []).slice(0, 30) + .map((c) => `[${c.author}] ${c.message}`) + .join("\n"); + + const prompt = `Tu es un expert en analyse de projets Git. Analyse ce dépôt et donne un résumé en français (5-8 phrases max) : + +**Projet** : ${stats.repoName} +**Période** : ${stats.activeDays} jours actifs, ${stats.totalCommits} commits + +**Statistiques clés** : +- ${stats.totalLinesAdded} lignes ajoutées / ${stats.totalLinesDeleted} supprimées +- ${stats.contributors.length} contributeur(s) : ${stats.contributors.slice(0, 5).map((c) => c.name).join(", ")} +- Types de commits : ${stats.commitTypes.filter((t) => t.count > 0).map((t) => `${t.type} (${t.percentage}%)`).join(", ")} +- Fichiers modifiés : ${stats.totalFilesChanged} + +**Derniers commits** : +${commitSamples} + +Réponds avec ce format : +1. 📊 **Activité** : tendance générale +2. 👥 **Équipe** : qui fait quoi +3. 🔧 **Code** : types de changements principaux +4. 💡 **Conseil** : une suggestion pertinente`; + + const response = await openai.chat.completions.create({ + model: selectedModel, + messages: [{ role: "user" as const, content: prompt }], + max_tokens: 500, + temperature: 0.7, + }); + + return response.choices[0]?.message?.content?.trim() || "Pas de résumé généré."; + } catch (error: any) { + return `Erreur Ollama (${selectedModel}) : ${error?.message || "inconnue"}. Vérifiez \`ollama pull ${selectedModel}\`.`; + } +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..569b506 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,63 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './src/pages/**/*.{js,ts,jsx,tsx,mdx}', + './src/components/**/*.{js,ts,jsx,tsx,mdx}', + './src/app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: 0 }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: 0 }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f0de5f0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { "@/*": ["./src/*"] } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}