diff --git a/.gitignore b/.gitignore index 747bbed4..76eaed1d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ coverage/ !.env.example *-lock.json *-lock.yaml +.idea \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7eb25986..f382c269 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,10 +15,12 @@ "better-sqlite3": "^12.4.1", "dotenv": "^17.2.3", "express": "^4.21.2", + "jszip": "^3.10.1", "lucide-react": "^0.546.0", "motion": "^12.23.24", "react": "^19.0.0", "react-dom": "^19.0.0", + "socket.io-client": "^4.8.3", "three": "^0.183.2", "vite": "^6.2.0" }, @@ -26,6 +28,7 @@ "@types/express": "^4.17.21", "@types/node": "^22.14.0", "autoprefixer": "^10.4.21", + "gh-pages": "^6.1.1", "tailwindcss": "^4.1.14", "tsx": "^4.21.0", "typescript": "~5.8.2", @@ -60,6 +63,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -796,6 +800,44 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1201,6 +1243,12 @@ "win32" ] }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", @@ -1528,6 +1576,7 @@ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/long": "^4.0.1", "@types/offscreencanvas": "~2019.7.0", @@ -1902,6 +1951,23 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2074,6 +2140,19 @@ "balanced-match": "^1.0.0" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -2093,6 +2172,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2346,6 +2426,23 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2399,6 +2496,12 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2500,6 +2603,19 @@ "node": ">=8" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dotenv": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", @@ -2553,6 +2669,13 @@ "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "license": "ISC" }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true, + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2577,6 +2700,49 @@ "once": "^1.4.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.20.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", @@ -2692,6 +2858,16 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2777,6 +2953,33 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "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==", + "dev": true, + "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/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2823,6 +3026,47 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -2856,6 +3100,38 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -2965,6 +3241,21 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3085,6 +3376,29 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/gh-pages": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.3.0.tgz", + "integrity": "sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.4", + "commander": "^13.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^11.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -3112,6 +3426,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/google-auth-library": { "version": "10.6.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.1.tgz", @@ -3269,6 +3617,22 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3290,6 +3654,16 @@ "node": ">= 0.10" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3299,6 +3673,35 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3368,6 +3771,61 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -3389,6 +3847,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", @@ -3638,6 +4105,19 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -3671,6 +4151,22 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3698,6 +4194,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3707,6 +4213,33 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -3972,6 +4505,35 @@ "wrappy": "1" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -3985,12 +4547,28 @@ "node": ">=8" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4000,6 +4578,16 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4037,6 +4625,16 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4048,6 +4646,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4055,6 +4654,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/postcss": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", @@ -4074,6 +4686,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4117,6 +4730,12 @@ "node": ">=10" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/protobufjs": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", @@ -4179,6 +4798,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "dev": true, + "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/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4223,6 +4863,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4232,6 +4873,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4296,6 +4938,17 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", @@ -4355,6 +5008,30 @@ "fsevents": "~2.3.2" } }, + "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==", + "dev": true, + "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/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4391,7 +5068,8 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/semver": { "version": "6.3.1", @@ -4456,6 +5134,12 @@ "node": ">= 0.8.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -4612,6 +5296,44 @@ "simple-concat": "^1.0.0" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4750,6 +5472,19 @@ "node": ">=0.10.0" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4831,6 +5566,19 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4846,6 +5594,19 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -4858,6 +5619,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -4917,6 +5679,16 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4985,6 +5757,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -5669,6 +6442,14 @@ } } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/src/App.tsx b/src/App.tsx index fe93810c..4c649b27 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,7 +21,9 @@ export default function App() { const [trainingStatus, setTrainingStatus] = useState(''); const [trainedModels, setTrainedModels] = useState<{ name: string }[]>([ { name: '跟随网球示例' }, - { name: '自动避障示例' } + { name: '自动避障示例' }, + { name: '找到小球并拾取示例' }, + { name: '找到红桶放下小球示例' } ]); const [selectedModel, setSelectedModel] = useState('跟随网球示例'); const [trainedModel, setTrainedModel] = useState<{ name: string } | null>(null); @@ -80,6 +82,8 @@ export default function App() { episodes: [] as any[], currentEpisode: [] as any[], target: null as THREE.Mesh | null, + bucket: null as THREE.Mesh | null, + draggingObject: null as THREE.Mesh | null, walls: [] as THREE.Mesh[], environmentGroup: null as THREE.Group | null, dirLight: null as THREE.DirectionalLight | null, @@ -174,6 +178,7 @@ export default function App() { sim.current.arm = { lowerArm, elbow, wrist, gripper, state: 'idle', + hasBall: false, grabbedObject: null, targetRotations: { lowerArm: Math.PI/4, elbow: Math.PI/2.5, wrist: -Math.PI/6 } }; @@ -187,23 +192,53 @@ export default function App() { // Dragging Logic const onMouseDown = (event: MouseEvent) => { - if (!container || !sim.current.camera || !sim.current.target) return; + // 防止在已经拖动时再次设置拖动对象 + if (isDragging.current) return; + + if (!container || !sim.current.camera) return; const rect = container.getBoundingClientRect(); mouse.current.x = ((event.clientX - rect.left) / container.clientWidth) * 2 - 1; mouse.current.y = -((event.clientY - rect.top) / container.clientHeight) * 2 + 1; raycaster.current.setFromCamera(mouse.current, sim.current.camera); - const intersects = raycaster.current.intersectObject(sim.current.target); - if (intersects.length > 0) { + // 优先检查目标对象,使用距离最近的 + const allIntersects = []; + + if (sim.current.target) { + const targetIntersects = raycaster.current.intersectObject(sim.current.target); + allIntersects.push(...targetIntersects.map(i => ({ ...i, object: sim.current.target }))); + } + + if (sim.current.bucket) { + const bucketIntersects = raycaster.current.intersectObject(sim.current.bucket); + allIntersects.push(...bucketIntersects.map(i => ({ ...i, object: sim.current.bucket }))); + } + + // 选择距离最近的对象 + if (allIntersects.length > 0) { + allIntersects.sort((a, b) => a.distance - b.distance); + const selectedObject = allIntersects[0].object; + const isTargetHeld = selectedObject === sim.current.target && ( + sim.current.arm?.hasBall || + sim.current.arm?.grabbedObject === sim.current.target || + sim.current.arm?.state === 'holding' + ); + + // 如果拖动的是小球且已被持有,则不允许拖动 + if (isTargetHeld) { + return; + } + + sim.current.draggingObject = selectedObject; isDragging.current = true; if (renderer.domElement) renderer.domElement.style.cursor = 'grabbing'; } }; const onMouseMove = (event: MouseEvent) => { - if (!container || !sim.current.camera || !sim.current.target) return; + if (!container || !sim.current.camera) return; const rect = container.getBoundingClientRect(); mouse.current.x = ((event.clientX - rect.left) / container.clientWidth) * 2 - 1; @@ -212,26 +247,37 @@ export default function App() { raycaster.current.setFromCamera(mouse.current, sim.current.camera); if (!isDragging.current) { - const intersects = raycaster.current.intersectObject(sim.current.target); + // 仅改变光标,不更新任何对象位置 + const targetIsHeld = !!sim.current.target && ( + sim.current.arm?.hasBall || + sim.current.arm?.grabbedObject === sim.current.target || + sim.current.arm?.state === 'holding' + ); + const targetIntersects = sim.current.target && !targetIsHeld ? raycaster.current.intersectObject(sim.current.target) : []; + const bucketIntersects = sim.current.bucket ? raycaster.current.intersectObject(sim.current.bucket) : []; + if (renderer.domElement) { - renderer.domElement.style.cursor = intersects.length > 0 ? 'grab' : 'default'; + renderer.domElement.style.cursor = (targetIntersects.length > 0 || bucketIntersects.length > 0) ? 'grab' : 'default'; } return; } - if (!sim.current.plane) return; + // 拖动时:只更新当前拖动的对象,与平面交集 + if (!sim.current.plane || !sim.current.draggingObject) return; - const intersects = raycaster.current.intersectObject(sim.current.plane); + const planeIntersects = raycaster.current.intersectObject(sim.current.plane); - if (intersects.length > 0) { - const point = intersects[0].point; - sim.current.target.position.x = point.x; - sim.current.target.position.z = point.z; + if (planeIntersects.length > 0) { + const point = planeIntersects[0].point; + // 明确只更新 draggingObject,不触及其他对象 + sim.current.draggingObject.position.x = point.x; + sim.current.draggingObject.position.z = point.z; } }; const onMouseUp = () => { isDragging.current = false; + sim.current.draggingObject = null; if (renderer.domElement) renderer.domElement.style.cursor = 'default'; }; @@ -461,10 +507,21 @@ export default function App() { const ball = new THREE.Mesh(ballGeo, ballMat); ball.position.set(0, 0.25, -5); ball.castShadow = true; - ball.userData = { w: 0.5, d: 0.5 }; + ball.userData = { w: 0.5, d: 0.5, isDraggable: true }; group.add(ball); sim.current.walls.push(ball); - sim.current.target = ball; // Use ball as the target since red cube is removed + sim.current.target = ball; + + // 红色方块(红桶) + const bucketGeo = new THREE.BoxGeometry(0.6, 0.6, 0.6); + const bucketMat = new THREE.MeshStandardMaterial({ color: 0xFF0000, roughness: 0.6 }); + const bucket = new THREE.Mesh(bucketGeo, bucketMat); + bucket.position.set(0, 0.3, 5); + bucket.castShadow = true; + bucket.userData = { w: 0.6, d: 0.6, isDraggable: true, isBucket: true }; + group.add(bucket); + sim.current.walls.push(bucket); + sim.current.bucket = bucket; addLog(`Scene updated: ${sceneType}, Size: ${sceneSize}, Complexity: ${sceneComplexity}`, 'info'); }, [sceneType, sceneSize, sceneComplexity, addLog]); @@ -802,7 +859,12 @@ export default function App() { } if (closestObj) { + if (closestObj === sim.current.target && sim.current.draggingObject === sim.current.target) { + isDragging.current = false; + sim.current.draggingObject = null; + } arm.grabbedObject = closestObj; + arm.hasBall = closestObj === sim.current.target; arm.state = 'picking_down'; arm.targetRotations = { lowerArm: Math.PI/2.2, elbow: Math.PI/4, wrist: Math.PI/4 }; addLog('Picking up object...', 'info'); @@ -852,6 +914,7 @@ export default function App() { // Drop to ground level arm.grabbedObject.position.y = 0.25; arm.grabbedObject = null; + arm.hasBall = false; } arm.state = 'dropping_up'; arm.targetRotations = { lowerArm: Math.PI/4, elbow: Math.PI/2.5, wrist: -Math.PI/6 }; @@ -1425,283 +1488,291 @@ export default function App() { if (!sim.current.isInferencing || !sim.current.target) return; const { robotState, target, model } = sim.current; + const armState = sim.current.arm?.state; + const isHolding = armState === 'holding'; + const isArmBusy = armState === 'picking_down' || armState === 'picking_up' || armState === 'dropping_down' || armState === 'dropping_up'; - if (selectedModel === '跟随网球示例' || selectedModel === '自动避障示例') { - let targetSpeed = 0; - let targetTurn = 0; + if (isArmBusy) { + robotState.velocity = 0; + robotState.angularVelocity = 0; + sim.current.inferenceTimeoutId = setTimeout(runInference, 100); + return; + } - if (selectedModel === '跟随网球示例') { - const dirX = Math.sin(robotState.rotation); - const dirZ = Math.cos(robotState.rotation); - - // Calculate from camera position (0.85 units forward) to match visual FOV - const camX = robotState.x + dirX * 0.85; - const camZ = robotState.z + dirZ * 0.85; - - const dx = target.position.x - camX; - const dz = target.position.z - camZ; - const distance = Math.sqrt(dx * dx + dz * dz); - - const targetAngle = Math.atan2(dx, dz); - let angleDiff = targetAngle - robotState.rotation; - - while (angleDiff > Math.PI) angleDiff -= Math.PI * 2; - while (angleDiff < -Math.PI) angleDiff += Math.PI * 2; - - // Camera horizontal FOV is ~96 degrees (48 left/right). Use 45 degrees for strict visibility. - const FOV = Math.PI / 4; - let isVisible = Math.abs(angleDiff) <= FOV; - - // Check for obstacles blocking the view - if (isVisible && distance > 1.0) { - const steps = Math.floor(distance / 0.5); - const stepX = dx / steps; - const stepZ = dz / steps; - let px = camX; - let pz = camZ; - - for (let i = 1; i < steps; i++) { - px += stepX; - pz += stepZ; - for (const wall of sim.current.walls) { - if (wall === target) continue; - const w = wall.userData.w; - const depth = wall.userData.d; - const wx = wall.position.x; - const wz = wall.position.z; - if (px > wx - w/2 && px < wx + w/2 && pz > wz - depth/2 && pz < wz + depth/2) { - isVisible = false; - break; - } + let targetSpeed = 0; + let targetTurn = 0; + + const computeFollow = (goal: THREE.Mesh) => { + const dirX = Math.sin(robotState.rotation); + const dirZ = Math.cos(robotState.rotation); + const camX = robotState.x + dirX * 0.85; + const camZ = robotState.z + dirZ * 0.85; + + const dx = goal.position.x - camX; + const dz = goal.position.z - camZ; + const distance = Math.sqrt(dx * dx + dz * dz); + + const targetAngle = Math.atan2(dx, dz); + let angleDiff = targetAngle - robotState.rotation; + while (angleDiff > Math.PI) angleDiff -= Math.PI * 2; + while (angleDiff < -Math.PI) angleDiff += Math.PI * 2; + + const FOV = Math.PI / 4; + let isVisible = Math.abs(angleDiff) <= FOV; + + if (isVisible && distance > 1.0) { + const steps = Math.max(1, Math.floor(distance / 0.5)); + const stepX = dx / steps; + const stepZ = dz / steps; + let px = camX; + let pz = camZ; + + for (let i = 1; i < steps; i++) { + px += stepX; + pz += stepZ; + for (const wall of sim.current.walls) { + if (wall === goal) continue; + const w = wall.userData.w; + const depth = wall.userData.d; + const wx = wall.position.x; + const wz = wall.position.z; + + if (px > wx - w / 2 && px < wx + w / 2 && pz > wz - depth / 2 && pz < wz + depth / 2) { + isVisible = false; + break; } - if (!isVisible) break; } + if (!isVisible) break; + } + } + + if (distance <= 1.2 && isVisible) { + targetSpeed = 0; + targetTurn = 0; + } else if (!isVisible) { + targetSpeed = 0; + targetTurn = turnSpeed; + } else { + targetTurn = angleDiff * 1.0; + targetTurn = Math.max(-turnSpeed * 1.5, Math.min(turnSpeed * 1.5, targetTurn)); + targetSpeed = Math.min(speed, (distance - 1.2) * 0.5); + if (Math.abs(angleDiff) > Math.PI / 6) { + targetSpeed *= 0.5; } + } + + return { distance, isVisible }; + }; + + if (selectedModel === '跟随网球示例' || selectedModel === '找到小球并拾取示例' || selectedModel === '找到红桶放下小球示例') { + let followGoal: THREE.Mesh; + let shouldFollow = true; - if (distance <= 1.5 && isVisible) { - // Reached the ball and it's visible: stop completely, no jittering + if (selectedModel === '找到小球并拾取示例') { + followGoal = target; + if (isHolding) { + // 拾取成功后停止移动 + shouldFollow = false; targetSpeed = 0; targetTurn = 0; - } else if (!isVisible) { - // Ball out of FOV or blocked: rotate in place to search + } + } else if (selectedModel === '找到红桶放下小球示例') { + followGoal = isHolding && sim.current.bucket ? sim.current.bucket : target; + if (!isHolding) { + // 如果没有持有球,停止移动等待用户手动拾取 + shouldFollow = false; targetSpeed = 0; - targetTurn = turnSpeed; - } else { - // Ball in FOV: track and approach - // Use a smaller multiplier for smoother turning - targetTurn = angleDiff * 1.0; - - // Cap the turn speed to prevent violent swings - targetTurn = Math.max(-turnSpeed * 1.5, Math.min(turnSpeed * 1.5, targetTurn)); - - // Smooth speed approach - targetSpeed = Math.min(speed, (distance - 1.5) * 0.5); - - // If the angle is too large, slow down to turn - if (Math.abs(angleDiff) > Math.PI / 6) { - targetSpeed *= 0.5; - } + targetTurn = 0; } - } else if (selectedModel === '自动避障示例') { - const checkDistance = (angleOffset: number) => { - const checkAngle = robotState.rotation + angleOffset; - const dirX = Math.sin(checkAngle); - const dirZ = Math.cos(checkAngle); - - let minDist = 5; // max lookahead - const robotRadius = 1.0; // slightly larger for safety + } else { + followGoal = target; + } - for (const wall of sim.current.walls) { - for (let d = 0.5; d < minDist; d += 0.5) { - const px = robotState.x + dirX * d; - const pz = robotState.z + dirZ * d; - - const w = wall.userData.w; - const depth = wall.userData.d; - const wx = wall.position.x; - const wz = wall.position.z; - - if (px > wx - w/2 - robotRadius && px < wx + w/2 + robotRadius && - pz > wz - depth/2 - robotRadius && pz < wz + depth/2 + robotRadius) { - minDist = d; - break; - } + if (shouldFollow) { + const { distance, isVisible } = computeFollow(followGoal); + + if (selectedModel === '找到小球并拾取示例' && !isHolding && distance <= 1.2 && isVisible) { + sim.current.keys['q'] = true; + } + + if (selectedModel === '找到红桶放下小球示例' && isHolding && sim.current.bucket && distance <= 1.2 && isVisible) { + sim.current.keys['e'] = true; + } + } + } else if (selectedModel === '自动避障示例') { + const checkDistance = (angleOffset: number) => { + const checkAngle = robotState.rotation + angleOffset; + const dirX = Math.sin(checkAngle); + const dirZ = Math.cos(checkAngle); + + let minDist = 5; + const robotRadius = 1.0; + + for (const wall of sim.current.walls) { + for (let d = 0.5; d < minDist; d += 0.5) { + const px = robotState.x + dirX * d; + const pz = robotState.z + dirZ * d; + const w = wall.userData.w; + const depth = wall.userData.d; + const wx = wall.position.x; + const wz = wall.position.z; + + if (px > wx - w / 2 - robotRadius && px < wx + w / 2 + robotRadius && + pz > wz - depth / 2 - robotRadius && pz < wz + depth / 2 + robotRadius) { + minDist = d; + break; } } - return minDist; - }; + } + return minDist; + }; - const distFront = checkDistance(0); - const distLeft = checkDistance(Math.PI / 4); - const distRight = checkDistance(-Math.PI / 4); + const distFront = checkDistance(0); + const distLeft = checkDistance(Math.PI / 4); + const distRight = checkDistance(-Math.PI / 4); - targetSpeed = speed * 0.8; - targetTurn = 0; + targetSpeed = speed * 0.8; + targetTurn = 0; - if (distFront < 3) { - targetSpeed = 0; - if (distLeft > distRight) { - targetTurn = turnSpeed; - } else { - targetTurn = -turnSpeed; - } - if (distLeft < 2 && distRight < 2) { - targetSpeed = -speed * 0.5; - targetTurn = turnSpeed; - } + if (distFront < 3) { + targetSpeed = 0; + if (distLeft > distRight) { + targetTurn = turnSpeed; } else { - if (distLeft < 3) targetTurn = -turnSpeed * 0.5; - if (distRight < 3) targetTurn = turnSpeed * 0.5; + targetTurn = -turnSpeed; + } + if (distLeft < 2 && distRight < 2) { + targetSpeed = -speed * 0.5; + targetTurn = turnSpeed; } + } else { + if (distLeft < 3) targetTurn = -turnSpeed * 0.5; + if (distRight < 3) targetTurn = turnSpeed * 0.5; } + } else { + if (!model) return; - targetSpeed = Math.max(-speed, Math.min(speed, targetSpeed)); - targetTurn = Math.max(-turnSpeed, Math.min(turnSpeed, targetTurn)); - - robotState.velocity = robotState.velocity * 0.5 + targetSpeed * 0.5; - robotState.angularVelocity = robotState.angularVelocity * 0.5 + targetTurn * 0.5; - - const newActiveKeys: Record = {}; - if (robotState.velocity > 0.02) newActiveKeys['w'] = true; - if (robotState.velocity < -0.02) newActiveKeys['s'] = true; - if (robotState.angularVelocity > 0.01) newActiveKeys['a'] = true; - if (robotState.angularVelocity < -0.01) newActiveKeys['d'] = true; - - setActiveKeys(prev => { - const keysChanged = Object.keys(newActiveKeys).length !== Object.keys(prev).length || - Object.keys(newActiveKeys).some(k => newActiveKeys[k] !== prev[k]); - return keysChanged ? newActiveKeys : prev; - }); - - sim.current.inferenceTimeoutId = setTimeout(runInference, 100); - return; - } - - if (!model) return; + if (typeof sim.current.lastX === 'undefined') { + sim.current.lastX = robotState.x; + sim.current.lastZ = robotState.z; + sim.current.stuckCounter = 0; + } - // Stuck detection logic - if (typeof sim.current.lastX === 'undefined') { + const distMoved = Math.sqrt( + Math.pow(robotState.x - sim.current.lastX, 2) + + Math.pow(robotState.z - sim.current.lastZ, 2) + ); sim.current.lastX = robotState.x; sim.current.lastZ = robotState.z; - sim.current.stuckCounter = 0; - } - const distMoved = Math.sqrt( - Math.pow(robotState.x - sim.current.lastX, 2) + - Math.pow(robotState.z - sim.current.lastZ, 2) - ); - sim.current.lastX = robotState.x; - sim.current.lastZ = robotState.z; + if (Math.abs(robotState.velocity) > 0.05 && distMoved < 0.005) { + sim.current.stuckCounter++; + } else { + sim.current.stuckCounter = Math.max(0, sim.current.stuckCounter - 1); + } - if (Math.abs(robotState.velocity) > 0.05 && distMoved < 0.005) { - sim.current.stuckCounter++; - } else { - sim.current.stuckCounter = Math.max(0, sim.current.stuckCounter - 1); - } + if (sim.current.stuckCounter > 20) { + addLog('Stuck detected! Recovering...', 'warning'); + robotState.velocity = -0.2; + robotState.angularVelocity = (Math.random() - 0.5) * 2.0; + sim.current.stuckCounter = 0; + sim.current.inferenceTimeoutId = setTimeout(runInference, 1500); + return; + } - if (sim.current.stuckCounter > 20) { - addLog('Stuck detected! Recovering...', 'warning'); - robotState.velocity = -0.2; - robotState.angularVelocity = (Math.random() - 0.5) * 2.0; // Increased rotation range - sim.current.stuckCounter = 0; - // Increase recovery time to 1.5s to allow robot to back away fully - sim.current.inferenceTimeoutId = setTimeout(runInference, 1500); - return; - } + const image = captureImage(); + if (!image) return; - const image = captureImage(); - if (!image) return; + const dx = target.position.x - robotState.x; + const dz = target.position.z - robotState.z; + const targetDist = Math.sqrt(dx * dx + dz * dz); - const dx = target.position.x - robotState.x; - const dz = target.position.z - robotState.z; - const targetDist = Math.sqrt(dx * dx + dz * dz); + const state = new Array(14).fill(0); + state[0] = robotState.x; + state[1] = robotState.z; + state[2] = robotState.rotation; + state[3] = robotState.velocity; + state[4] = targetDist; - const state = new Array(14).fill(0); - state[0] = robotState.x; - state[1] = robotState.z; - state[2] = robotState.rotation; - state[3] = robotState.velocity; - state[4] = targetDist; + const prediction = actService.predict(model, image, state); - const prediction = actService.predict(model, image, state); + if (!sim.current.actionBuffer) sim.current.actionBuffer = []; - // Temporal Ensembling (simplified) - if (!sim.current.actionBuffer) sim.current.actionBuffer = []; + const newChunk: any[] = []; + for (let i = 0; i < actService.CHUNK_SIZE; i++) { + newChunk.push({ + v: prediction[i * 2], + w: prediction[i * 2 + 1], + weight: Math.exp(-0.5 * i) + }); + } - // Parse prediction into chunk - const newChunk = []; - for (let i = 0; i < actService.CHUNK_SIZE; i++) { - newChunk.push({ - v: prediction[i * 2], - w: prediction[i * 2 + 1], - weight: Math.exp(-0.5 * i) // Exponential weighting - }); - } - setActionChunks(newChunk.map(a => a.v)); - sim.current.actionBuffer.push(newChunk); + setActionChunks(newChunk.map(a => a.v)); + sim.current.actionBuffer.push(newChunk); - // Keep buffer size limited - if (sim.current.actionBuffer.length > actService.CHUNK_SIZE) { - sim.current.actionBuffer.shift(); - } + if (sim.current.actionBuffer.length > actService.CHUNK_SIZE) { + sim.current.actionBuffer.shift(); + } + + let sumV = 0; + let sumW = 0; + let totalWeight = 0; + + sim.current.actionBuffer.forEach((chunk: any[], index: number) => { + const offset = sim.current.actionBuffer.length - 1 - index; + if (offset < chunk.length) { + const action = chunk[offset]; + sumV += action.v * action.weight; + sumW += action.w * action.weight; + totalWeight += action.weight; + } + }); - // Aggregate actions - let sumV = 0, sumW = 0, totalWeight = 0; + if (totalWeight > 0) { + const targetV = Math.max(-speed, Math.min(speed, sumV / totalWeight)); + const targetW = Math.max(-turnSpeed, Math.min(turnSpeed, sumW / totalWeight)); - sim.current.actionBuffer.forEach((chunk: any[], index: number) => { - const offset = sim.current.actionBuffer.length - 1 - index; - if (offset < chunk.length) { - const action = chunk[offset]; - sumV += action.v * action.weight; - sumW += action.w * action.weight; - totalWeight += action.weight; + robotState.velocity = robotState.velocity * 0.5 + targetV * 0.5; + robotState.angularVelocity = robotState.angularVelocity * 0.5 + targetW * 0.5; + } else { + robotState.velocity = Math.max(-speed, Math.min(speed, prediction[0])); + robotState.angularVelocity = Math.max(-turnSpeed, Math.min(turnSpeed, prediction[1])); } - }); - if (totalWeight > 0) { - // Apply slight smoothing to velocity to prevent sudden jumps - const targetV = Math.max(-speed, Math.min(speed, sumV / totalWeight)); - const targetW = Math.max(-turnSpeed, Math.min(turnSpeed, sumW / totalWeight)); + const newActiveKeys: Record = {}; + if (robotState.velocity > 0.02) newActiveKeys['w'] = true; + if (robotState.velocity < -0.02) newActiveKeys['s'] = true; + if (robotState.angularVelocity > 0.01) newActiveKeys['a'] = true; + if (robotState.angularVelocity < -0.01) newActiveKeys['d'] = true; - // Simple low-pass filter (alpha = 0.5) - robotState.velocity = robotState.velocity * 0.5 + targetV * 0.5; - robotState.angularVelocity = robotState.angularVelocity * 0.5 + targetW * 0.5; + setActiveKeys(prev => { + const keysChanged = Object.keys(newActiveKeys).length !== Object.keys(prev).length || + Object.keys(newActiveKeys).some(k => newActiveKeys[k] !== prev[k]); + return keysChanged ? newActiveKeys : prev; + }); - // Log movement periodically (every 200ms) - if (!sim.current.lastInferenceLogTime || Date.now() - sim.current.lastInferenceLogTime > 200) { - addLog(`Inference Movement: V=${robotState.velocity.toFixed(2)}, W=${robotState.angularVelocity.toFixed(2)}`, 'info'); - sim.current.lastInferenceLogTime = Date.now(); - } - } else { - robotState.velocity = Math.max(-speed, Math.min(speed, prediction[0])); - robotState.angularVelocity = Math.max(-turnSpeed, Math.min(turnSpeed, prediction[1])); + sim.current.inferenceTimeoutId = setTimeout(runInference, 100); + return; } - // Update active keys for UI highlighting during inference + targetSpeed = Math.max(-speed, Math.min(speed, targetSpeed)); + targetTurn = Math.max(-turnSpeed, Math.min(turnSpeed, targetTurn)); + + robotState.velocity = robotState.velocity * 0.5 + targetSpeed * 0.5; + robotState.angularVelocity = robotState.angularVelocity * 0.5 + targetTurn * 0.5; + const newActiveKeys: Record = {}; if (robotState.velocity > 0.02) newActiveKeys['w'] = true; if (robotState.velocity < -0.02) newActiveKeys['s'] = true; if (robotState.angularVelocity > 0.01) newActiveKeys['a'] = true; if (robotState.angularVelocity < -0.01) newActiveKeys['d'] = true; - + setActiveKeys(prev => { - const keysChanged = Object.keys(newActiveKeys).length !== Object.keys(prev).length || - Object.keys(newActiveKeys).some(k => newActiveKeys[k] !== prev[k]); + const keysChanged = Object.keys(newActiveKeys).length !== Object.keys(prev).length || + Object.keys(newActiveKeys).some(k => newActiveKeys[k] !== prev[k]); return keysChanged ? newActiveKeys : prev; }); - // Check goal - if (targetDist < 1.0) { - addLog('Target reached!', 'success'); - target.position.set( - (Math.random() - 0.5) * 12, - 0.4, - (Math.random() - 0.5) * 12 - ); - sim.current.actionBuffer = []; - } - - // Match training frequency (10Hz = 100ms) sim.current.inferenceTimeoutId = setTimeout(runInference, 100); }, [addLog, captureImage, speed, turnSpeed, selectedModel]); @@ -1720,8 +1791,17 @@ export default function App() { } try { - if (selectedModel === '跟随网球示例' || selectedModel === '自动避障示例') { + if (selectedModel === '跟随网球示例' || selectedModel === '自动避障示例' || selectedModel === '找到小球并拾取示例' || selectedModel === '找到红桶放下小球示例') { addLog(`Loading ${selectedModel}...`, 'info'); + + // 检查初始状态 + if (selectedModel === '找到小球并拾取示例' && sim.current.arm?.hasBall) { + addLog('警告: 初始状态已拾取到小球,此示例应从未拾取状态开始', 'warning'); + } + if (selectedModel === '找到红桶放下小球示例' && !sim.current.arm?.hasBall) { + addLog('警告: 初始状态未拾取到小球,请先手动拾取小球', 'warning'); + } + sim.current.isInferencing = true; setIsInferencing(true); addLog(`Starting Frontend ACT inference with ${selectedModel}...`, 'success'); diff --git a/src/sim/environment.ts b/src/sim/environment.ts index 6d71deeb..f90f7afd 100644 --- a/src/sim/environment.ts +++ b/src/sim/environment.ts @@ -89,9 +89,19 @@ export function updateEnvironment( const ball = new THREE.Mesh(ballGeo, ballMat); ball.position.set(-halfSize/2 + 1.5, 0.25, halfSize/2 - 1.5); ball.castShadow = true; - ball.userData = { w: 0.5, d: 0.5 }; + ball.userData = { w: 0.5, d: 0.5, isDraggable: true }; group.add(ball); walls.push(ball); - return { walls, group, target: null }; + // 红桶(红色方块) + const bucketGeo = new THREE.BoxGeometry(0.6, 0.6, 0.6); + const bucketMat = new THREE.MeshStandardMaterial({ color: 0xFF0000, roughness: 0.6 }); + const bucket = new THREE.Mesh(bucketGeo, bucketMat); + bucket.position.set(halfSize/2 - 2, 0.3, -halfSize/2 + 2); + bucket.castShadow = true; + bucket.userData = { w: 0.6, d: 0.6, isDraggable: true, isBucket: true }; + group.add(bucket); + walls.push(bucket); + + return { walls, group, target: null, bucket }; }