From b669206a6e4948dadbf6df1e2dc1d4c9800b4fe8 Mon Sep 17 00:00:00 2001 From: tharvik Date: Mon, 17 Jun 2024 17:12:22 +0200 Subject: [PATCH 01/18] readme: add some papers --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a95b960e5..2af25e88d 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ ___ DISCO aims to enable open-access and easy-use distributed training which is - :tornado: efficient ([R1](https://github.com/epfml/powergossip), [R2](https://github.com/epfml/ChocoSGD)) - :lock: privacy-preserving ([R3](https://eprint.iacr.org/2017/281.pdf), [R4](https://arxiv.org/abs/2006.04747)) -- :hammer_and_wrench: fault-tolerant and dynamic over time ([R5](https://arxiv.org/abs/1910.12308)) -- :ninja: robust to malicious actors and data poisoning ([R6](https://arxiv.org/abs/2012.10333), [R7](https://arxiv.org/abs/2006.09365)) -- :apple: :banana: interpretable in imperfectly interoperable data distributions ([R8](https://arxiv.org/abs/2107.06580)) -- :mirror: personalizable ([R9](https://arxiv.org/abs/2103.00710)) +- :hammer_and_wrench: fault-tolerant and dynamic over time ([R5](https://arxiv.org/abs/2106.06639), [R6](https://arxiv.org/abs/2206.08307)) +- :ninja: robust to malicious actors and data poisoning ([R7](https://arxiv.org/abs/2012.10333), [R8](https://arxiv.org/abs/2006.09365)) +- :apple: :banana: interpretable in imperfectly interoperable data distributions ([R9](https://arxiv.org/abs/2107.06580)) +- :mirror: personalizable ([R10](https://arxiv.org/abs/2103.00710)) - :carrot: fairly incentivize participation From 7e659dae92dbe5efdb8225c840a88f943816d972 Mon Sep 17 00:00:00 2001 From: tharvik Date: Wed, 19 Jun 2024 11:05:00 +0200 Subject: [PATCH 02/18] *: bump deps --- discojs/src/serialization/weights.ts | 4 +- package-lock.json | 1055 ++++++++--------- server/package.json | 2 +- webapp/src/components/containers/IconCard.vue | 2 - .../data/dataset_input/FileSelection.vue | 10 +- 5 files changed, 523 insertions(+), 550 deletions(-) diff --git a/discojs/src/serialization/weights.ts b/discojs/src/serialization/weights.ts index 81f7c8c55..fd978a1d1 100644 --- a/discojs/src/serialization/weights.ts +++ b/discojs/src/serialization/weights.ts @@ -23,8 +23,8 @@ function isSerialized (raw: unknown): raw is Serialized { } const _: Serialized = { - shape: shape as number[], - data: data as number[], + shape: shape, + data: data, } return true diff --git a/package-lock.json b/package-lock.json index 6fbf43eb5..45086c1ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,9 +108,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", - "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -228,9 +228,9 @@ "link": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -244,9 +244,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -260,9 +260,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -276,9 +276,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -292,9 +292,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -308,9 +308,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -324,9 +324,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -340,9 +340,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -356,9 +356,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -372,9 +372,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -388,9 +388,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -404,9 +404,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -420,9 +420,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -436,9 +436,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -452,9 +452,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -468,9 +468,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -484,9 +484,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -500,9 +500,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -516,9 +516,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -532,9 +532,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -564,9 +564,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -580,9 +580,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -611,9 +611,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -700,6 +700,7 @@ "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", @@ -749,6 +750,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@isaacs/cliui": { @@ -1364,16 +1366,16 @@ "dev": true }, "node_modules/@tensorflow/tfjs": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.19.0.tgz", - "integrity": "sha512-d2A1lTc6my7GJ5LwqzXa+igJ5+18exwsnaphZ3roi5nJ197uwxVSMIc2vSJnqZz1KajC5/mZgQr67EZrpTFlBg==", - "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.19.0", - "@tensorflow/tfjs-backend-webgl": "4.19.0", - "@tensorflow/tfjs-converter": "4.19.0", - "@tensorflow/tfjs-core": "4.19.0", - "@tensorflow/tfjs-data": "4.19.0", - "@tensorflow/tfjs-layers": "4.19.0", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.20.0.tgz", + "integrity": "sha512-+ZLfJq2jyIOE2/+yKPoyD/gfy3RZypbfMrlzvBDgodTK5jnexprihhX38hxilh9HPWvWQXJqiUjKJP5ECCikrw==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.20.0", + "@tensorflow/tfjs-backend-webgl": "4.20.0", + "@tensorflow/tfjs-converter": "4.20.0", + "@tensorflow/tfjs-core": "4.20.0", + "@tensorflow/tfjs-data": "4.20.0", + "@tensorflow/tfjs-layers": "4.20.0", "argparse": "^1.0.10", "chalk": "^4.1.0", "core-js": "3.29.1", @@ -1385,9 +1387,9 @@ } }, "node_modules/@tensorflow/tfjs-backend-cpu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.19.0.tgz", - "integrity": "sha512-7pT05Ea6GTXjbqRgkmayZRYvaiNl3LLk1TyfUvC8iIqMw5d7p4Wgte2pfM2gMbIZ/opOxURhFYuI0FiQvUrW6g==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.20.0.tgz", + "integrity": "sha512-1QRQ6AqAa/VB8JOArf5nY3Dc/QQHXbfuxgdIdQhKrABEHgvlaWt2Vv696UhIlVl75YoNY+vWlCwBdGQIKYfFGw==", "dependencies": { "@types/seedrandom": "^2.4.28", "seedrandom": "^3.0.5" @@ -1396,15 +1398,15 @@ "yarn": ">= 1.3.2" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.19.0" + "@tensorflow/tfjs-core": "4.20.0" } }, "node_modules/@tensorflow/tfjs-backend-webgl": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.19.0.tgz", - "integrity": "sha512-R0DC1W65lqTOccCwxMhH+VOKCgSrhd9GEejIIGhjeXt6oZlACFnOx4SuUr/qKLCDsL5I4E9iFLxAJMmsfYvARw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.20.0.tgz", + "integrity": "sha512-M03fJonJGxm2u3SCzRNA2JLh0gxaAye64SEmGAXOehizowxy42l+lMsPWU8xU7r7mN6PEilBNkuKAf5YJ7Xumg==", "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.19.0", + "@tensorflow/tfjs-backend-cpu": "4.20.0", "@types/offscreencanvas": "~2019.3.0", "@types/seedrandom": "^2.4.28", "seedrandom": "^3.0.5" @@ -1413,21 +1415,21 @@ "yarn": ">= 1.3.2" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.19.0" + "@tensorflow/tfjs-core": "4.20.0" } }, "node_modules/@tensorflow/tfjs-converter": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.19.0.tgz", - "integrity": "sha512-xIOE6enaVHPYCXKpHxJnUlN8hzlcQkgFSymHjBmdDnNCresuRwBGz4dqYAQMeQG21Ei3lxCQFdDDH7aSvUEAPw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.20.0.tgz", + "integrity": "sha512-UJ2ntQ1TNtVHB5qGMwB0j306bs3KH1E1HKJ9Dxvrc6PUaivOV+CPKqmbidOFG5LylXeRC36JBdhe+gVT2nFHNw==", "peerDependencies": { - "@tensorflow/tfjs-core": "4.19.0" + "@tensorflow/tfjs-core": "4.20.0" } }, "node_modules/@tensorflow/tfjs-core": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.19.0.tgz", - "integrity": "sha512-GZ0d53PG0HGQCC7hbWv1qDnZctHYe/cafHZrBY5eNeQjQE6fBr3NsR5GfLadT0TELwmX9/nyritGDzvy6xmzHQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.20.0.tgz", + "integrity": "sha512-m/cc9qDc63al9UhdbXRUYTLGfJJlhuN5tylAX/2pJMLj32c8a6ThGDJYoKzpf32n5g3MQGYLchjClDxeGdXMPQ==", "dependencies": { "@types/long": "^4.0.1", "@types/offscreencanvas": "~2019.7.0", @@ -1485,16 +1487,16 @@ } }, "node_modules/@tensorflow/tfjs-data": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.19.0.tgz", - "integrity": "sha512-n0ZgJp5UhhBatohUt9pXSCCApusK+1Flyk6yDrQYuxOTjhRppd6jYrF7LCDG3hMFi3QLGl0jab1zYrn9BwtC/w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.20.0.tgz", + "integrity": "sha512-k6S8joXhoXkatcoT6mYCxBzRCsnrLfnl6xjLe46SnXO0oEEy4Vuzbmp5Ydl1uU2hHr73zL91EdAC1k8Hng/+oA==", "dependencies": { "@types/node-fetch": "^2.1.2", "node-fetch": "~2.6.1", "string_decoder": "^1.3.0" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.19.0", + "@tensorflow/tfjs-core": "4.20.0", "seedrandom": "^3.0.5" } }, @@ -1537,27 +1539,27 @@ } }, "node_modules/@tensorflow/tfjs-layers": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.19.0.tgz", - "integrity": "sha512-NufvuRaZdIyoG+R13d7oL8G5Bywox+ihPMiMZ3tWU+me8C8Y0pVC69mrnhOS9R8an7GDxKKSTTNEZhUvPvMGiQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.20.0.tgz", + "integrity": "sha512-SCHZH29Vyw+Y9eoaJHiaNo6yqM9vD3XCKncoczonRRywejm3FFqddg1AuWAfSE9XoNPE21o9PsknvKLl/Uh+Cg==", "peerDependencies": { - "@tensorflow/tfjs-core": "4.19.0" + "@tensorflow/tfjs-core": "4.20.0" } }, "node_modules/@tensorflow/tfjs-node": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-4.19.0.tgz", - "integrity": "sha512-1HLIAuu5azP8SW7t5EZc1W5VOdjWndJYz1N1agz0It/tMtnuWIdAfcY08VjfuiI/NhAwuPShehqv6CZ3SYh+Vg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-4.20.0.tgz", + "integrity": "sha512-pVSOlzsVqh5ck3aiNPJCltB3ASKjsLqNPvJ28lXn9Xg648U4eHDk8G47m9w4uf0FdVcWDfjPM3hDCbBZ/E2KXg==", "hasInstallScript": true, "dependencies": { "@mapbox/node-pre-gyp": "1.0.9", - "@tensorflow/tfjs": "4.19.0", + "@tensorflow/tfjs": "4.20.0", "adm-zip": "^0.5.2", "google-protobuf": "^3.9.2", "https-proxy-agent": "^2.2.1", "progress": "^2.0.0", "rimraf": "^2.6.2", - "tar": "^4.4.6" + "tar": "^6.2.1" }, "engines": { "node": ">=8.11.0" @@ -1609,22 +1611,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@tensorflow/tfjs-node/node_modules/https-proxy-agent": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", @@ -1668,69 +1654,6 @@ "rimraf": "bin.js" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "dependencies": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/@tensorflow/tfjs-node/node_modules/tar/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/@tensorflow/tfjs-node/node_modules/tar/node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dependencies": { - "minipass": "^2.6.0" - } - }, - "node_modules/@tensorflow/tfjs-node/node_modules/tar/node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/@tensorflow/tfjs-node/node_modules/tar/node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dependencies": { - "minipass": "^2.9.0" - } - }, - "node_modules/@tensorflow/tfjs-node/node_modules/tar/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/@tensorflow/tfjs-node/node_modules/tar/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, "node_modules/@tensorflow/tfjs/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1930,9 +1853,9 @@ } }, "node_modules/@types/d3-force": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", - "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", "dev": true }, "node_modules/@types/d3-format": { @@ -2075,9 +1998,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.1.tgz", - "integrity": "sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA==", + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", "dev": true, "dependencies": { "@types/node": "*", @@ -2110,9 +2033,9 @@ "dev": true }, "node_modules/@types/jsdom": { - "version": "21.1.6", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.6.tgz", - "integrity": "sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==", + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", "dev": true, "dependencies": { "@types/node": "*", @@ -2132,9 +2055,9 @@ "dev": true }, "node_modules/@types/mocha": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", - "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", + "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==", "dev": true }, "node_modules/@types/msgpack-lite": { @@ -2147,9 +2070,9 @@ } }, "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", "dependencies": { "undici-types": "~5.26.4" } @@ -2256,16 +2179,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz", - "integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz", + "integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/type-utils": "7.11.0", - "@typescript-eslint/utils": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/type-utils": "7.14.1", + "@typescript-eslint/utils": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2289,15 +2212,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz", - "integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz", + "integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/typescript-estree": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "debug": "^4.3.4" }, "engines": { @@ -2317,13 +2240,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz", - "integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz", + "integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0" + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2334,13 +2257,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz", - "integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz", + "integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.11.0", - "@typescript-eslint/utils": "7.11.0", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/utils": "7.14.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2361,9 +2284,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz", - "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz", + "integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2374,13 +2297,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz", - "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz", + "integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2402,15 +2325,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz", - "integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz", + "integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/typescript-estree": "7.11.0" + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2424,12 +2347,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz", - "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz", + "integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", + "@typescript-eslint/types": "7.14.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2447,9 +2370,9 @@ "dev": true }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", - "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", + "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" @@ -2513,9 +2436,9 @@ } }, "node_modules/@vitest/expect/node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "dependencies": { "type-detect": "^4.0.0" @@ -2643,64 +2566,62 @@ } }, "node_modules/@volar/language-core": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.2.5.tgz", - "integrity": "sha512-2htyAuxRrAgETmFeUhT4XLELk3LiEcqoW/B8YUXMF6BrGWLMwIR09MFaZYvrA2UhbdAeSyeQ726HaWSWkexUcQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.3.4.tgz", + "integrity": "sha512-wXBhY11qG6pCDAqDnbBRFIDSIwbqkWI7no+lj5+L7IlA7HRIjRP7YQLGzT0LF4lS6eHkMSsclXqy9DwYJasZTQ==", "dev": true, "dependencies": { - "@volar/source-map": "2.2.5" + "@volar/source-map": "2.3.4" } }, "node_modules/@volar/source-map": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.2.5.tgz", - "integrity": "sha512-wrOEIiZNf4E+PWB0AxyM4tfhkfldPsb3bxg8N6FHrxJH2ohar7aGu48e98bp3pR9HUA7P/pR9VrLmkTrgCCnWQ==", - "dev": true, - "dependencies": { - "muggle-string": "^0.4.0" - } + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.3.4.tgz", + "integrity": "sha512-C+t63nwcblqLIVTYXaVi/+gC8NukDaDIQI72J3R7aXGvtgaVB16c+J8Iz7/VfOy7kjYv7lf5GhBny6ACw9fTGQ==", + "dev": true }, "node_modules/@volar/typescript": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.2.5.tgz", - "integrity": "sha512-eSV/n75+ppfEVugMC/salZsI44nXDPAyL6+iTYCNLtiLHGJsnMv9GwiDMujrvAUj/aLQyqRJgYtXRoxop2clCw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.3.4.tgz", + "integrity": "sha512-acCvt7dZECyKcvO5geNybmrqOsu9u8n5XP1rfiYsOLYGPxvHRav9BVmEdRyZ3vvY6mNyQ1wLL5Hday4IShe17w==", "dev": true, "dependencies": { - "@volar/language-core": "2.2.5", - "path-browserify": "^1.0.1" + "@volar/language-core": "2.3.4", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", - "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.30.tgz", + "integrity": "sha512-ZL8y4Xxdh8O6PSwfdZ1IpQ24PjTAieOz3jXb/MDTfDtANcKBMxg1KLm6OX2jofsaQGYfIVzd3BAG22i56/cF1w==", "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/shared": "3.4.27", + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.30", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", - "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.30.tgz", + "integrity": "sha512-+16Sd8lYr5j/owCbr9dowcNfrHd+pz+w2/b5Lt26Oz/kB90C9yNbxQ3bYOvt7rI2bxk0nqda39hVcwDFw85c2Q==", "dependencies": { - "@vue/compiler-core": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-core": "3.4.30", + "@vue/shared": "3.4.30" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", - "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", - "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/compiler-core": "3.4.27", - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.30.tgz", + "integrity": "sha512-8vElKklHn/UY8+FgUFlQrYAPbtiSB2zcgeRKW7HkpSRn/JjMRmZvuOtwDx036D1aqKNSTtXkWRfqx53Qb+HmMg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.30", + "@vue/compiler-dom": "3.4.30", + "@vue/compiler-ssr": "3.4.30", + "@vue/shared": "3.4.30", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.38", @@ -2708,18 +2629,18 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", - "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.30.tgz", + "integrity": "sha512-ZJ56YZGXJDd6jky4mmM0rNaNP6kIbQu9LTKZDhcpddGe/3QIalB1WHHmZ6iZfFNyj5mSypTa4+qDJa5VIuxMSg==", "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-dom": "3.4.30", + "@vue/shared": "3.4.30" } }, "node_modules/@vue/devtools-api": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", - "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.3.tgz", + "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==" }, "node_modules/@vue/eslint-config-prettier": { "version": "9.0.0", @@ -2760,16 +2681,17 @@ } }, "node_modules/@vue/language-core": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.19.tgz", - "integrity": "sha512-A9EGOnvb51jOvnCYoRLnMP+CcoPlbZVxI9gZXE/y2GksRWM6j/PrLEIC++pnosWTN08tFpJgxhSS//E9v/Sg+Q==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.22.tgz", + "integrity": "sha512-dNTAAtEOuMiz7N1s5tKpypnVVCtawxVSF5BukD0ELcYSw+DSbrSlYYSw8GuwvurodCeYFSHsmslE+c2sYDNoiA==", "dev": true, "dependencies": { - "@volar/language-core": "~2.2.4", + "@volar/language-core": "~2.3.1", "@vue/compiler-dom": "^3.4.0", "@vue/shared": "^3.4.0", "computeds": "^0.0.1", "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "vue-template-compiler": "^2.7.14" }, @@ -2783,48 +2705,49 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", - "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.30.tgz", + "integrity": "sha512-bVJurnCe3LS0JII8PPoAA63Zd2MBzcKrEzwdQl92eHCcxtIbxD2fhNwJpa+KkM3Y/A4T5FUnmdhgKwOf6BfbcA==", "dependencies": { - "@vue/shared": "3.4.27" + "@vue/shared": "3.4.30" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", - "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.30.tgz", + "integrity": "sha512-qaFEbnNpGz+tlnkaualomogzN8vBLkgzK55uuWjYXbYn039eOBZrWxyXWq/7qh9Bz2FPifZqGjVDl/FXiq9L2g==", "dependencies": { - "@vue/reactivity": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/reactivity": "3.4.30", + "@vue/shared": "3.4.30" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", - "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.30.tgz", + "integrity": "sha512-tV6B4YiZRj5QsaJgw2THCy5C1H+2UeywO9tqgWEc21tn85qHEERndHN/CxlyXvSBFrpmlexCIdnqPuR9RM9thw==", "dependencies": { - "@vue/runtime-core": "3.4.27", - "@vue/shared": "3.4.27", + "@vue/reactivity": "3.4.30", + "@vue/runtime-core": "3.4.30", + "@vue/shared": "3.4.30", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", - "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.30.tgz", + "integrity": "sha512-TBD3eqR1DeDc0cMrXS/vEs/PWzq1uXxnvjoqQuDGFIEHFIwuDTX/KWAQKIBjyMWLFHEeTDGYVsYci85z2UbTDg==", "dependencies": { - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-ssr": "3.4.30", + "@vue/shared": "3.4.30" }, "peerDependencies": { - "vue": "3.4.27" + "vue": "3.4.30" } }, "node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==" + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.30.tgz", + "integrity": "sha512-CLg+f8RQCHQnKvuHY9adMsMaQOcqclh6Z5V9TaoMgy0ut0tz848joZ7/CYFFyF/yZ5i2yaw7Fn498C+CNZVHIg==" }, "node_modules/@vue/test-utils": { "version": "2.4.6", @@ -2848,9 +2771,9 @@ "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==" }, "node_modules/@xenova/transformers": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.1.tgz", - "integrity": "sha512-zo702tQAFZXhzeD2GCYUNUqeqkoueOdiSbQWa4s0q7ZE4z8WBIwIsMMPGobpgdqjQ2u0Qulo08wuqVEUrBXjkQ==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz", + "integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==", "dependencies": { "@huggingface/jinja": "^0.2.2", "onnxruntime-web": "1.14.0", @@ -2883,9 +2806,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2904,20 +2827,23 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } }, "node_modules/adm-zip": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.12.tgz", - "integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==", + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.14.tgz", + "integrity": "sha512-DnyqqifT4Jrcvb8USYjp6FHtBpEIz1mnXu6pTRHZ0RL69LbQYiO+0lDFg5+OKA7U29oWSs3a/i8fhn8ZcceIWg==", "engines": { - "node": ">=6.0" + "node": ">=12.0" } }, "node_modules/agent-base": { @@ -3034,9 +2960,9 @@ } }, "node_modules/apexcharts": { - "version": "3.49.1", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.49.1.tgz", - "integrity": "sha512-MqGtlq/KQuO8j0BBsUJYlRG8VBctKwYdwuBtajHgHTmSgUU3Oai+8oYN/rKCXwXzrUlYA+GiMgotAIbXY2BCGw==", + "version": "3.49.2", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.49.2.tgz", + "integrity": "sha512-vBB8KgwfD9rSObA7s4kY2rU6DeaN67gTR3JN7r32ztgKVf8lKkdFQ6iUhk6oIHrV7W8PoHhr5EwKymn0z5Fz6A==", "dependencies": { "@yr/monotone-cubic-spline": "^1.0.3", "svg.draggable.js": "^2.2.2", @@ -3286,26 +3212,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/bare-events": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", - "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", "optional": true }, "node_modules/bare-fs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.0.tgz", - "integrity": "sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", "optional": true, "dependencies": { "bare-events": "^2.0.0", "bare-path": "^2.0.0", - "bare-stream": "^1.0.0" + "bare-stream": "^2.0.0" } }, "node_modules/bare-os": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.3.0.tgz", - "integrity": "sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", "optional": true }, "node_modules/bare-path": { @@ -3318,12 +3244,12 @@ } }, "node_modules/bare-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-1.0.0.tgz", - "integrity": "sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", "optional": true, "dependencies": { - "streamx": "^2.16.1" + "streamx": "^2.18.0" } }, "node_modules/base64-js": { @@ -3763,9 +3689,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001637", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001637.tgz", - "integrity": "sha512-1x0qRI1mD1o9e+7mBI7XtzFAP4XszbHaVWsMiGbSPLYekKTJF7K+FNk6AsXH4sUpc+qrsI3pVgf1Jdl/uGkuSQ==", + "version": "1.0.30001638", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz", + "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", "dev": true, "funding": [ { @@ -3836,16 +3762,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -3858,6 +3778,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -4407,9 +4330,9 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cypress": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.10.0.tgz", - "integrity": "sha512-tOhwRlurVOQbMduX+KonoMeQILs2cwR3yHGGENoFvvSoLUBHmJ8b9/n21gFSDqjlOJ+SRVcwuh+fG/JDsHsT6Q==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", + "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", "hasInstallScript": true, "dependencies": { "@cypress/request": "^3.0.0", @@ -4895,9 +4818,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dependencies": { "ms": "2.1.2" }, @@ -4943,9 +4866,9 @@ } }, "node_modules/deep-eql": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz", - "integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "engines": { "node": ">=6" @@ -5336,9 +5259,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -5348,29 +5271,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -5466,9 +5389,9 @@ } }, "node_modules/eslint-plugin-cypress": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-3.2.0.tgz", - "integrity": "sha512-HaxMz6BoU4ay+K4WrG9ZJC1NdX06FqSlAwtRDStjM0ORFT7zCNPNuRJ+kUPc17Rt2AMUBSqeD9L0zTR3uZhPpw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-3.3.0.tgz", + "integrity": "sha512-HPHMPzYBIshzJM8wqgKSKHG2p/8R0Gbg4Pb3tcdC9WrmkuqxiKxSKbjunUrajhV5l7gCIFrh1P7C7GuBqH6YuQ==", "dev": true, "dependencies": { "globals": "^13.20.0" @@ -5793,9 +5716,9 @@ } }, "node_modules/express-ws/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, @@ -6112,9 +6035,9 @@ } }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -6894,11 +6817,14 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7095,9 +7021,9 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "node_modules/jackspeak": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", - "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -7113,18 +7039,18 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, "bin": { "jiti": "bin/jiti.js" } }, "node_modules/joi": { - "version": "17.13.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", - "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", "dev": true, "dependencies": { "@hapi/hoek": "^9.3.0", @@ -7165,15 +7091,16 @@ } }, "node_modules/js-beautify/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { @@ -7806,9 +7733,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -7876,26 +7803,26 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "node_modules/mlly": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz", - "integrity": "sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", "dev": true, "dependencies": { "acorn": "^8.11.3", "pathe": "^1.1.2", - "pkg-types": "^1.1.0", + "pkg-types": "^1.1.1", "ufo": "^1.5.3" } }, "node_modules/mocha": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.5.2.tgz", + "integrity": "sha512-9btlN3JKCefPf+vKd/kcKz2SXxi12z6JswkGfaAF0saQvnsqLJk504ZmbxhSoENge08E9dsymozKgFMTl5PQsA==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.3", + "chokidar": "^3.5.3", "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", @@ -7931,6 +7858,29 @@ "node": ">=6" } }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/mocha/node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -8021,9 +7971,9 @@ } }, "node_modules/nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -8062,9 +8012,9 @@ } }, "node_modules/node-abi": { - "version": "3.62.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", - "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", + "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", "dependencies": { "semver": "^7.3.5" }, @@ -8222,9 +8172,9 @@ } }, "node_modules/node-datachannel": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.9.1.tgz", - "integrity": "sha512-b6Uc6YN5We2/aZA6QGicxSdWUDSwlR+vcO/Dn44BY5gieF3AOwsL/zPD+Ril+1KvYDwVJkVbRIovbG76E4PpwA==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.9.2.tgz", + "integrity": "sha512-sknq4EAJcSNHUMiK6Gru3vtwo8QICiFs5xJyaitJy/ZLSUbikaGrDORpIpdGp8++4AYQOYC5vr9VyA6i01KeJQ==", "hasInstallScript": true, "peer": true, "dependencies": { @@ -8343,9 +8293,9 @@ "dev": true }, "node_modules/nodemon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.1.tgz", - "integrity": "sha512-k43xGaDtaDIcufn0Fc6fTtsdKSkV/hQzoQFigNH//GaKta28yoKVYXCnV+KXRqfT/YzsFaQU9VdeEG+HEyxr6A==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -8532,9 +8482,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8742,6 +8695,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -9144,9 +9103,9 @@ } }, "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "dev": true, "engines": { "node": ">=14" @@ -9228,9 +9187,9 @@ } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", "dev": true, "peer": true, "bin": { @@ -9749,9 +9708,9 @@ } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, "node_modules/rimraf": { "version": "3.0.2", @@ -9819,9 +9778,9 @@ } }, "node_modules/rrweb-cssom": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.0.tgz", - "integrity": "sha512-KlSv0pm9kgQSRxXEMgtivPJ4h826YHsuob8pSHcfSZsSXGtvpEAie8S0AnXuObEJ7nhikOb4ahwxDm0H2yW17g==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", "dev": true }, "node_modules/run-parallel": { @@ -10342,15 +10301,15 @@ "dev": true }, "node_modules/start-server-and-test": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.0.3.tgz", - "integrity": "sha512-QsVObjfjFZKJE6CS6bSKNwWZCKBG6975/jKRPPGFfFh+yOQglSeGXiNWjzgQNXdphcBI9nXbyso9tPfX4YAUhg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.0.4.tgz", + "integrity": "sha512-CKNeBTcP0hVqIlNismHMudb9q3lLdAjcVPO13/7gfI66fcJpeIb/o4NzQd1JK/CD+lfWVqr10ZH9Y14+OwlJuw==", "dev": true, "dependencies": { "arg": "^5.0.2", "bluebird": "3.7.2", "check-more-types": "2.24.0", - "debug": "4.3.4", + "debug": "4.3.5", "execa": "5.1.1", "lazy-ass": "1.6.0", "ps-tree": "1.2.0", @@ -10455,12 +10414,13 @@ } }, "node_modules/streamx": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", - "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" @@ -10613,15 +10573,16 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { @@ -10804,9 +10765,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", - "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", + "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -10887,6 +10848,14 @@ "node": ">=6" } }, + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11144,9 +11113,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/tty-browserify": { "version": "0.0.1", @@ -11216,9 +11185,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -11229,14 +11198,14 @@ } }, "node_modules/typescript-eslint": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.11.0.tgz", - "integrity": "sha512-ZKe3yHF/IS/kCUE4CGE3UgtK+Q7yRk1e9kwEI0rqm9XxMTd9P1eHe0LVVtrZ3oFuIQ2unJ9Xn0vTsLApzJ3aPw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.14.1.tgz", + "integrity": "sha512-Eo1X+Y0JgGPspcANKjeR6nIqXl4VL5ldXLc15k4m9upq+eY5fhU2IueiEZL6jmHrKH8aCfbIvM/v3IrX5Hg99w==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "7.11.0", - "@typescript-eslint/parser": "7.11.0", - "@typescript-eslint/utils": "7.11.0" + "@typescript-eslint/eslint-plugin": "7.14.1", + "@typescript-eslint/parser": "7.14.1", + "@typescript-eslint/utils": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -11410,9 +11379,9 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -11445,9 +11414,9 @@ } }, "node_modules/vee-validate": { - "version": "4.12.8", - "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.12.8.tgz", - "integrity": "sha512-A07rm3+y7SRk0CMD/O4nBT0nxtwjyfzGZwjEUDk18SxK0ZMzd4AFCzzdHlIiCE1QgHetxd0I3kVkZdN0GG0Oww==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.13.1.tgz", + "integrity": "sha512-JAlUWTBHg0z66n+v66mrtE9IC1xmVCggzpyc7UXCNkizVok8Zgt1VAVjobSxA/0N19Zn6v6hRfjoYciYH/Z11Q==", "dependencies": { "@vue/devtools-api": "^6.6.1", "type-fest": "^4.8.3" @@ -11457,9 +11426,9 @@ } }, "node_modules/vee-validate/node_modules/type-fest": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.3.tgz", - "integrity": "sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ==", + "version": "4.20.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.20.1.tgz", + "integrity": "sha512-R6wDsVsoS9xYOpy8vgeBlqpdOyzJ12HNfQhC/aAKWM3YoCV9TtunJzh/QpkMgeDhkoynDcw5f1y+qF9yc/HHyg==", "engines": { "node": ">=16" }, @@ -11481,12 +11450,12 @@ } }, "node_modules/vite": { - "version": "5.2.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", - "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", + "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", + "esbuild": "^0.21.3", "postcss": "^8.4.38", "rollup": "^4.13.0" }, @@ -11678,9 +11647,9 @@ } }, "node_modules/vitest/node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "dependencies": { "type-detect": "^4.0.0" @@ -11847,16 +11816,22 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "dev": true + }, "node_modules/vue": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", - "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.30.tgz", + "integrity": "sha512-NcxtKCwkdf1zPsr7Y8+QlDBCGqxvjLXF2EX+yi76rV5rrz90Y6gK1cq0olIhdWGgrlhs9ElHuhi9t3+W5sG5Xw==", "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-sfc": "3.4.27", - "@vue/runtime-dom": "3.4.27", - "@vue/server-renderer": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-dom": "3.4.30", + "@vue/compiler-sfc": "3.4.30", + "@vue/runtime-dom": "3.4.30", + "@vue/server-renderer": "3.4.30", + "@vue/shared": "3.4.30" }, "peerDependencies": { "typescript": "*" @@ -11868,15 +11843,15 @@ } }, "node_modules/vue-component-type-helpers": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.19.tgz", - "integrity": "sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.22.tgz", + "integrity": "sha512-gPr2Ba7efUwy/Vfbuf735bHSVdN4ycoZUCHfypkI33M9DUH+ieRblLLVM2eImccFYaWNWwEzURx02EgoXDBmaQ==", "dev": true }, "node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -11899,9 +11874,9 @@ } }, "node_modules/vue-eslint-parser": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", - "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", "dev": true, "dependencies": { "debug": "^4.3.4", @@ -11923,9 +11898,9 @@ } }, "node_modules/vue-router": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz", - "integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.0.tgz", + "integrity": "sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==", "dependencies": { "@vue/devtools-api": "^6.5.1" }, @@ -11958,13 +11933,13 @@ } }, "node_modules/vue-tsc": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.19.tgz", - "integrity": "sha512-JWay5Zt2/871iodGF72cELIbcAoPyhJxq56mPPh+M2K7IwI688FMrFKc/+DvB05wDWEuCPexQJ6L10zSwzzapg==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.22.tgz", + "integrity": "sha512-lMBIwPBO0sxCcmvu45yt1b035AaQ8/XSXQDk8m75y4j0jSXY/y/XzfEtssQ9JMS47lDaR10O3/926oCs8OeGUw==", "dev": true, "dependencies": { - "@volar/typescript": "~2.2.4", - "@vue/language-core": "2.0.19", + "@volar/typescript": "~2.3.1", + "@vue/language-core": "2.0.22", "semver": "^7.5.4" }, "bin": { @@ -12290,9 +12265,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", - "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", "dev": true, "bin": { "yaml": "bin.mjs" @@ -12405,7 +12380,7 @@ "express-ws": "5", "immutable": "4", "msgpack-lite": "0.1", - "uuid": "9" + "uuid": "10" }, "bin": { "disco-server": "dist/run_server.js" @@ -12451,7 +12426,7 @@ "@vue/eslint-config-typescript": "13", "@vue/test-utils": "2", "@vue/tsconfig": "0.5", - "autoprefixer": "^10.4.19", + "autoprefixer": "10", "eslint-plugin-cypress": "3", "jsdom": "24", "postcss": "8", @@ -12462,7 +12437,7 @@ "vite-plugin-node-polyfills": "0.22", "vitest": "1", "vue-tsc": "2", - "vue3-spinners": "^1.2.2" + "vue3-spinners": "1" } } } diff --git a/server/package.json b/server/package.json index ab902f69c..fbf1b3d35 100644 --- a/server/package.json +++ b/server/package.json @@ -25,7 +25,7 @@ "express-ws": "5", "immutable": "4", "msgpack-lite": "0.1", - "uuid": "9" + "uuid": "10" }, "devDependencies": { "@types/chai": "4", diff --git a/webapp/src/components/containers/IconCard.vue b/webapp/src/components/containers/IconCard.vue index d7247bd10..c9d4ae9bd 100644 --- a/webapp/src/components/containers/IconCard.vue +++ b/webapp/src/components/containers/IconCard.vue @@ -38,8 +38,6 @@ diff --git a/webapp/src/components/containers/ButtonsCard.vue b/webapp/src/components/containers/ButtonsCard.vue new file mode 100644 index 000000000..cdc31dfe2 --- /dev/null +++ b/webapp/src/components/containers/ButtonsCard.vue @@ -0,0 +1,54 @@ + + + diff --git a/webapp/src/components/containers/__tests__/ButtonsCard.spec.ts b/webapp/src/components/containers/__tests__/ButtonsCard.spec.ts new file mode 100644 index 000000000..9804766ef --- /dev/null +++ b/webapp/src/components/containers/__tests__/ButtonsCard.spec.ts @@ -0,0 +1,32 @@ +import { expect, it, vi } from "vitest"; +import { mount } from "@vue/test-utils"; + +import { List } from "immutable"; + +import ButtonsCard from "../ButtonsCard.vue"; + +it("shows buttons", async () => { + const wrapper = mount(ButtonsCard, { + props: { + buttons: List.of( + ["first", () => {}] as const, + ["second", () => {}] as const, + ["third", () => {}] as const, + ), + }, + }); + + expect(wrapper.findAll("button")).toHaveLength(3); +}); + +it("triggers action on click", async () => { + const button = ["click", vi.fn(() => {})] as const; + + const wrapper = mount(ButtonsCard, { + props: { buttons: List.of(button) }, + }); + + wrapper.get("button").trigger("click"); + + expect(button[1]).toHaveBeenCalled(); +}); diff --git a/webapp/src/components/home/GetStarted.vue b/webapp/src/components/home/GetStarted.vue index 9779f4a5a..6bae4e696 100644 --- a/webapp/src/components/home/GetStarted.vue +++ b/webapp/src/components/home/GetStarted.vue @@ -1,52 +1,60 @@ diff --git a/webapp/src/components/home/Home.vue b/webapp/src/components/home/Home.vue index fc591a766..959e8bc44 100644 --- a/webapp/src/components/home/Home.vue +++ b/webapp/src/components/home/Home.vue @@ -4,26 +4,22 @@
- [DIS]tributed - [CO]llaborative Learning + [DIS]tributed + [CO]llaborative Learning
- - + + diff --git a/webapp/src/components/home/Landing.vue b/webapp/src/components/home/Landing.vue index 2ed338971..7f4ea198c 100644 --- a/webapp/src/components/home/Landing.vue +++ b/webapp/src/components/home/Landing.vue @@ -1,34 +1,31 @@ diff --git a/webapp/src/components/pages/TaskList.vue b/webapp/src/components/pages/TaskList.vue index 4e7545ef5..e3cfa39b9 100644 --- a/webapp/src/components/pages/TaskList.vue +++ b/webapp/src/components/pages/TaskList.vue @@ -2,21 +2,16 @@
- - - - +
- - - - + +
+
@@ -82,11 +73,13 @@ import { computed } from 'vue' import { useRouter } from 'vue-router' import { storeToRefs } from 'pinia' +import { List } from "immutable"; + import type { Task } from '@epfml/discojs' import { useTasksStore } from '@/store/tasks' import { useTrainingStore } from '@/store/training' -import ButtonCard from '@/components/containers/ButtonCard.vue' +import ButtonsCard from '@/components/containers/ButtonsCard.vue' import IconCard from '@/components/containers/IconCard.vue' import Tasks from '@/assets/svg/Tasks.vue' import DISCOllaborative from '@/components/simple/DISCOllaborative.vue' diff --git a/webapp/src/components/testing/Testing.vue b/webapp/src/components/testing/Testing.vue index 70bcc3dab..68c1677f7 100644 --- a/webapp/src/components/testing/Testing.vue +++ b/webapp/src/components/testing/Testing.vue @@ -18,45 +18,39 @@ :key="path" class="contents" > - - - - - + +

+

+ Date: + {{ metadata.date }} at {{ metadata.hours }} +

+

+ Size:{{ metadata.fileSize }} kB +

+

+ Type:{{ metadata.type === 'saved' ? 'Saved' : 'Cached' }} +

+
+ @@ -94,21 +88,16 @@ :key="task.id" class="contents" > - - - - + + Download the latest {{ task.displayInformation.taskTitle }} model available on the remote server. + @@ -172,7 +161,7 @@ import { useTasksStore } from '@/store/tasks' import { useValidationStore } from '@/store/validation' import Data from '@/components/data/Data.vue' import Tester from '@/components/testing/Tester.vue' -import ButtonCard from '@/components/containers/ButtonCard.vue' +import ButtonsCard from '@/components/containers/ButtonsCard.vue' import IconCard from '@/components/containers/IconCard.vue' const validationStore = useValidationStore() diff --git a/webapp/src/components/training/Finished.vue b/webapp/src/components/training/Finished.vue index f24f62416..6dd846314 100644 --- a/webapp/src/components/training/Finished.vue +++ b/webapp/src/components/training/Finished.vue @@ -1,37 +1,24 @@ @@ -39,6 +26,8 @@ import { computed } from 'vue' import { useRouter } from 'vue-router' +import { List } from "immutable"; + import type { Task, ModelInfo } from '@epfml/discojs' import { EmptyMemory, Memory } from '@epfml/discojs' import { IndexedDB } from '@epfml/discojs-web' @@ -46,7 +35,7 @@ import { IndexedDB } from '@epfml/discojs-web' import { useMemoryStore } from '@/store/memory' import { useValidationStore } from '@/store/validation' import { useToaster } from '@/composables/toaster' -import ButtonCard from '@/components/containers/ButtonCard.vue' +import ButtonsCard from '@/components/containers/ButtonsCard.vue' interface Props { task: Task } From 6caabcbfbc80f573fca96a3454b5eafef6ab5083 Mon Sep 17 00:00:00 2001 From: tharvik Date: Wed, 19 Jun 2024 13:53:16 +0200 Subject: [PATCH 04/18] webapp: commonize branding --- webapp/src/components/home/GetStarted.vue | 6 +++--- webapp/src/components/home/Home.vue | 4 ++-- webapp/src/components/information/Further.vue | 5 +++-- webapp/src/components/information/Information.vue | 4 ++-- webapp/src/components/information/Tutorial.vue | 5 +++-- webapp/src/components/pages/TaskList.vue | 5 +++-- webapp/src/components/progress_bars/InformationBar.vue | 5 +++-- webapp/src/components/progress_bars/TrainingBar.vue | 3 +-- webapp/src/components/sidebar/SideBar.vue | 7 ++----- webapp/src/components/simple/DISCO.vue | 8 ++++++++ webapp/src/components/simple/DISCOllaborative.vue | 10 +++++----- webapp/src/components/testing/Testing.vue | 4 ++-- webapp/src/components/training/Finished.vue | 5 +++-- 13 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 webapp/src/components/simple/DISCO.vue diff --git a/webapp/src/components/home/GetStarted.vue b/webapp/src/components/home/GetStarted.vue index 6bae4e696..7e0545ef1 100644 --- a/webapp/src/components/home/GetStarted.vue +++ b/webapp/src/components/home/GetStarted.vue @@ -9,8 +9,7 @@ > @@ -43,8 +42,9 @@ import { useRouter } from "vue-router"; import { List } from "immutable"; -import ButtonsCard from "@/components/containers/ButtonsCard.vue"; +import DISCO from "@/components/simple/DISCO.vue"; import DISCOllaborative from "@/components/simple/DISCOllaborative.vue"; +import ButtonsCard from "@/components/containers/ButtonsCard.vue"; const router = useRouter(); diff --git a/webapp/src/components/home/Home.vue b/webapp/src/components/home/Home.vue index 959e8bc44..b0d86d4eb 100644 --- a/webapp/src/components/home/Home.vue +++ b/webapp/src/components/home/Home.vue @@ -4,9 +4,9 @@
- [DIS][DIS]tributed - [CO][CO]llaborative Learning
diff --git a/webapp/src/components/information/Further.vue b/webapp/src/components/information/Further.vue index a43462855..9f3a3ac3f 100644 --- a/webapp/src/components/information/Further.vue +++ b/webapp/src/components/information/Further.vue @@ -24,7 +24,7 @@
Foundation technology -
DISCO uses a public model-private data approach. +
uses a public model-private data approach. The models are trained on the web app via
Research-focused design -
DISCO aims to enable decentralized training of +
aims to enable decentralized training of machine learning algorithms, which is (i) efficient (
{ informationStore.step = 3 }) diff --git a/webapp/src/components/information/Information.vue b/webapp/src/components/information/Information.vue index bfe7aceb0..31ed382c1 100644 --- a/webapp/src/components/information/Information.vue +++ b/webapp/src/components/information/Information.vue @@ -3,8 +3,8 @@

- dis-tributed  - co-llaborative  + distributed  + collaborative  learning platform

diff --git a/webapp/src/components/information/Tutorial.vue b/webapp/src/components/information/Tutorial.vue index 3c6925532..e4e0d94fe 100644 --- a/webapp/src/components/information/Tutorial.vue +++ b/webapp/src/components/information/Tutorial.vue @@ -6,7 +6,7 @@ @@ -36,6 +36,7 @@ import { useMemoryStore } from '@/store/memory' import { useValidationStore } from '@/store/validation' import { useToaster } from '@/composables/toaster' import ButtonsCard from '@/components/containers/ButtonsCard.vue' +import DISCOllaborative from '@/components/simple/DISCOllaborative.vue' interface Props { task: Task } From 02ca008823514c760e5fe7332f8790d7faffa917 Mon Sep 17 00:00:00 2001 From: tharvik Date: Wed, 19 Jun 2024 15:10:57 +0200 Subject: [PATCH 05/18] webapp/task_creation_form: reduce exported --- webapp/src/task_creation_form.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/webapp/src/task_creation_form.ts b/webapp/src/task_creation_form.ts index 67e964fe3..7c968c676 100644 --- a/webapp/src/task_creation_form.ts +++ b/webapp/src/task_creation_form.ts @@ -1,6 +1,6 @@ import * as yup from 'yup' -export interface FormElement { +interface FormElement { key: string default: string } @@ -38,7 +38,7 @@ const otherReq = (req: any) => { } } -export const generalInformation: FormSection = { +const generalInformation: FormSection = { id: 'generalInformation', title: 'General Information', fields: [ @@ -70,7 +70,8 @@ export const generalInformation: FormSection = { } ] } -export const displayInformation: FormSection = { + +const displayInformation: FormSection = { id: 'displayInformation', title: 'Task & Model Description', fields: [ @@ -359,7 +360,7 @@ export const privacyParameters: FormSection = { ] } -export const modelFiles: FormSection = { +const modelFiles: FormSection = { id: 'modelFiles', title: 'Model Files', fields: [ @@ -401,7 +402,3 @@ export const sections: FormSection[] = [ privacyParameters, modelFiles ] - -export interface FormProps { - section: FormSection -} From 8379f1a37e3d172ab57aabf0412e73acbe85b0b4 Mon Sep 17 00:00:00 2001 From: tharvik Date: Mon, 24 Jun 2024 15:11:53 +0200 Subject: [PATCH 06/18] webapp/container/IconCardSmall: avoid slots --- .../components/containers/IconCardSmall.vue | 40 ++++----------- .../training/TrainingInformation.vue | 49 +++++++------------ 2 files changed, 29 insertions(+), 60 deletions(-) diff --git a/webapp/src/components/containers/IconCardSmall.vue b/webapp/src/components/containers/IconCardSmall.vue index c3ee1a837..38f5cd523 100644 --- a/webapp/src/components/containers/IconCardSmall.vue +++ b/webapp/src/components/containers/IconCardSmall.vue @@ -1,48 +1,28 @@ - diff --git a/webapp/src/components/training/TrainingInformation.vue b/webapp/src/components/training/TrainingInformation.vue index 3e899569e..04b19b852 100644 --- a/webapp/src/components/training/TrainingInformation.vue +++ b/webapp/src/components/training/TrainingInformation.vue @@ -2,38 +2,27 @@
- - - - + + - - - - + + + - - - - + +
From b3226e038e36b131c6891659c0dd9aa7477d1d57 Mon Sep 17 00:00:00 2001 From: tharvik Date: Fri, 21 Jun 2024 11:04:12 +0200 Subject: [PATCH 07/18] discojs/model: expose batch generator --- .github/workflows/lint-test-build.yml | 2 +- cli/src/benchmark_gpt.ts | 10 +- cli/src/cli.ts | 9 +- discojs/src/default_tasks/wikitext.ts | 2 +- discojs/src/index.ts | 3 +- discojs/src/models/gpt/evaluate.ts | 3 +- discojs/src/models/gpt/gpt.spec.ts | 4 +- discojs/src/models/gpt/index.ts | 124 ++++++--- discojs/src/models/gpt/model.ts | 35 ++- discojs/src/models/index.ts | 3 +- discojs/src/models/logs.ts | 42 +++ discojs/src/models/model.ts | 19 +- discojs/src/models/tfjs.ts | 127 +++++---- discojs/src/training/disco.ts | 62 +++-- discojs/src/training/trainer/trainer.ts | 71 +++-- discojs/src/utils/async_iterator.spec.ts | 63 +++++ discojs/src/utils/async_iterator.ts | 79 ++++++ docs/examples/README.md | 4 +- docs/examples/training.ts | 8 +- docs/examples/wikitext.ts | 4 +- server/tests/e2e/federated.spec.ts | 23 +- webapp/cypress/e2e/library.cy.ts | 4 +- webapp/cypress/e2e/training.cy.ts | 4 +- webapp/src/charts.ts | 73 ----- webapp/src/components/training/Trainer.vue | 79 ++++-- .../training/TrainingInformation.vue | 251 +++++++++++------- .../training/__tests__/Trainer.spec.ts | 4 +- .../__tests__/TrainingInformation.spec.ts | 15 +- 28 files changed, 742 insertions(+), 385 deletions(-) create mode 100644 discojs/src/models/logs.ts create mode 100644 discojs/src/utils/async_iterator.spec.ts create mode 100644 discojs/src/utils/async_iterator.ts delete mode 100644 webapp/src/charts.ts diff --git a/.github/workflows/lint-test-build.yml b/.github/workflows/lint-test-build.yml index 479df3a6a..30371dc10 100644 --- a/.github/workflows/lint-test-build.yml +++ b/.github/workflows/lint-test-build.yml @@ -335,7 +335,7 @@ jobs: cache: npm - run: npm ci - run: npm --workspace={discojs,discojs-node,server} run build - - run: npm --workspace=cli start -- -t cifar10 -u 3 -e 1 + - run: npm --workspace=cli start -- -t cifar10 -u 3 -e 1 -r 1 test-docs-examples: needs: [build-lib, build-lib-node, build-server, download-datasets] diff --git a/cli/src/benchmark_gpt.ts b/cli/src/benchmark_gpt.ts index 4a2b20c70..f748ddbf1 100644 --- a/cli/src/benchmark_gpt.ts +++ b/cli/src/benchmark_gpt.ts @@ -1,6 +1,6 @@ import { parse } from 'ts-command-line-args'; import type { Task } from '@epfml/discojs' -import { fetchTasks, data, models } from '@epfml/discojs' +import { fetchTasks, data, models, async_iterator } from '@epfml/discojs' import { NodeTextLoader, loadModelFromDisk } from '@epfml/discojs-node' import { startServer } from 'server' @@ -57,7 +57,7 @@ async function main(args: Required): Promise { */ if (!benchmarkInference) { // Benchmark parameters - const epoch = 1 + const epochsCount = 1 const iterationsPerEpoch = 10 const config: models.GPTConfig = { @@ -80,10 +80,10 @@ async function main(args: Required): Promise { console.log(`\tmodel type ${modelType} \n\tbatch size ${batchSize} \n\tcontext length ${contextLength}`) let epochTime = performance.now() - const logGenerator = model.train(preprocessedDataset, undefined, epoch) - for await (const logs of logGenerator) { + for (let epochsCounter = 0; epochsCounter < epochsCount; epochsCounter++) { + const [_, logs] = await async_iterator.gather(model.train(preprocessedDataset)) epochTime = (performance.now() - epochTime) - const msPerToken = epochTime / (batchSize * contextLength * iterationsPerEpoch * epoch) + const msPerToken = epochTime / (batchSize * contextLength * iterationsPerEpoch * epochsCounter) console.log(`\t\tTraining time: ${msPerToken.toFixed(2)} ms/token
${logs.peakMemory.toFixed(2)} GB`) } diff --git a/cli/src/cli.ts b/cli/src/cli.ts index e49f2f5a4..128605fc3 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -2,7 +2,7 @@ import { List, Range } from 'immutable' import fs from 'node:fs/promises' import type { data, RoundLogs, Task } from '@epfml/discojs' -import { Disco, aggregator as aggregators, client as clients } from '@epfml/discojs' +import { Disco, aggregator as aggregators, async_iterator, client as clients } from '@epfml/discojs' import { startServer } from 'server' import { getTaskData } from './data.js' @@ -23,7 +23,12 @@ async function runUser( const disco = new Disco(task, { scheme: "federated", client }); let logs = List(); - for await (const round of disco.fit(data)) logs = logs.push(round); + for await (const round of disco.fit(data)) { + const [roundGen, roundLogs] = async_iterator.split(round) + for await (const epoch of roundGen) + for await (const _ of epoch); + logs = logs.push(await roundLogs); + } await disco.close(); return logs; diff --git a/discojs/src/default_tasks/wikitext.ts b/discojs/src/default_tasks/wikitext.ts index 85291f473..244a6baba 100644 --- a/discojs/src/default_tasks/wikitext.ts +++ b/discojs/src/default_tasks/wikitext.ts @@ -22,7 +22,7 @@ export const wikitext: TaskProvider = { modelID: 'llm-raw-model', preprocessingFunctions: [data.TextPreprocessing.Tokenize, data.TextPreprocessing.LeftPadding], scheme: 'federated', - epochs: 5, + epochs: 6, // Unused by wikitext because data already comes split // But if set to 0 then the webapp doesn't display the validation metrics validationSplit: 0.1, diff --git a/discojs/src/index.ts b/discojs/src/index.ts index 278471c56..9d44c5798 100644 --- a/discojs/src/index.ts +++ b/discojs/src/index.ts @@ -13,10 +13,11 @@ export { Memory, type ModelInfo, type Path, type ModelSource, Empty as EmptyMemo export { Disco, RoundLogs } from './training/index.js' export { Validator } from './validation/index.js' -export { Model, EpochLogs } from './models/index.js' +export { Model, BatchLogs, EpochLogs, ValidationMetrics } from './models/index.js' export * as models from './models/index.js' export * from './task/index.js' export * as defaultTasks from './default_tasks/index.js' export * from './types.js' +export * as async_iterator from "./utils/async_iterator.js" diff --git a/discojs/src/models/gpt/evaluate.ts b/discojs/src/models/gpt/evaluate.ts index 1fedd420a..0165c2018 100644 --- a/discojs/src/models/gpt/evaluate.ts +++ b/discojs/src/models/gpt/evaluate.ts @@ -9,7 +9,7 @@ export default async function evaluate ( model: tf.LayersModel, dataset: tf.data.Dataset, maxEvalBatches: number -): Promise> { +): Promise> { let datasetSize = 0 let totalLoss = 0 const acc: [number, number] = [0, 0] @@ -53,7 +53,6 @@ export default async function evaluate ( return { val_loss: loss, val_perplexity: Math.exp(loss), - acc: acc[0] / acc[1], val_acc: acc[0] / acc[1] } } diff --git a/discojs/src/models/gpt/gpt.spec.ts b/discojs/src/models/gpt/gpt.spec.ts index 669ceb9a2..c4b55649d 100644 --- a/discojs/src/models/gpt/gpt.spec.ts +++ b/discojs/src/models/gpt/gpt.spec.ts @@ -36,8 +36,8 @@ describe('gpt-tfjs', function() { }).repeat().batch(64) const model = new GPT(config) - const logGenerator = model.train(tokenDataset, undefined, 5) // 5 epochs - for await (const _ of logGenerator); // Await the end of training + for (let i = 0; i < 5; i++) + for await (const _ of model.train(tokenDataset, undefined)); const generation = await model.generate("Lorem ipsum dolor", tokenizer, 1) expect(generation).equal(data) // Assert that the model completes 'Lorem ipsum dolor' with 'sit' }) diff --git a/discojs/src/models/gpt/index.ts b/discojs/src/models/gpt/index.ts index b9753ff97..8f7f7b2b0 100644 --- a/discojs/src/models/gpt/index.ts +++ b/discojs/src/models/gpt/index.ts @@ -8,10 +8,13 @@ import { PreTrainedTokenizer } from '@xenova/transformers'; import { WeightsContainer } from '../../index.js' import type { Dataset } from '../../dataset/index.js' -import { Model } from '../model.js' +import { BatchLogs, Model, EpochLogs } from "../index.js"; +import type { Prediction, Sample } from '../model.js' + import { GPTForCausalLM } from './model.js' -import type { EpochLogs, Prediction, Sample } from '../model.js' -import type { GPTConfig } from './config.js' +import { DEFAULT_CONFIG, type GPTConfig } from './config.js' +import evaluate from './evaluate.js'; +import { List } from 'immutable'; export type GPTSerialization = { weights: WeightsContainer @@ -21,9 +24,13 @@ export type GPTSerialization = { export class GPT extends Model { private readonly model: GPTForCausalLM + readonly #maxBatchCount: number + constructor (partialConfig?: GPTConfig, layersModel?: tf.LayersModel) { super() + this.model = new GPTForCausalLM(partialConfig, layersModel) + this.#maxBatchCount = partialConfig?.maxIter ?? DEFAULT_CONFIG.maxIter } /** @@ -38,51 +45,90 @@ export class GPT extends Model { override async *train( trainingData: Dataset, validationData?: Dataset, - epochs = 1, - ): AsyncGenerator { - this.model.compile() + ): AsyncGenerator { + this.model.compile(); + + const batches = await trainingData.iterator(); // tf.LazyIterator isn't an AsyncGenerator + let batchesLogs = List(); + for ( + let batchNumber = 0; + batchNumber < this.#maxBatchCount; + batchNumber++ + ) { + const iteration = await batches.next(); + if (iteration.done) break; + const batch = iteration.value; + + const batchLogs = await this.#runBatch(batch); + tf.dispose(batch); + + yield batchLogs; + batchesLogs = batchesLogs.push(batchLogs); + } + + const validation = validationData && (await this.#evaluate(validationData)); + return new EpochLogs(batchesLogs, validation); + } + + async #runBatch( + batch: tf.TensorContainer, + ): Promise { let logs: tf.Logs | undefined; - const trainingArgs: tf.ModelFitDatasetArgs = { - epochs: 1, // force fitDataset to do only one epoch because it is wrapped in a for loop - validationData, - callbacks: { onEpochEnd: (_, cur) => { logs = cur }}, + await this.model.fitDataset(tf.data.array([batch]), { + epochs: 1, + verbose: 0, // don't pollute + callbacks: { + onEpochEnd: (_, cur) => { + logs = cur; + }, + }, + }); + if (logs === undefined) throw new Error("batch didn't gave any logs"); + + const { loss, acc: accuracy } = logs; + if (loss === undefined || isNaN(loss)) + throw new Error("training loss is undefined or NaN"); + + return { + accuracy, + loss, + memoryUsage: tf.memory().numBytes / 1024 / 1024 / 1024, }; - for (let epoch = 0; epoch < epochs; epoch++) { - await this.model.fitDataset(trainingData, trainingArgs); - if (logs === undefined) { - throw new Error("Epoch didn't gave any logs"); - } - const { loss, val_acc, val_loss, peakMemory } = logs; - if (loss === undefined || isNaN(loss)) { - throw new Error("Training loss is undefined or nan"); - } - const structuredLogs: EpochLogs = { - epoch, - peakMemory, - training: { - loss: logs.loss, - accuracy: logs.acc - } - } + } - if (validationData !== undefined) { - if(val_loss === undefined || isNaN(val_loss) || - val_acc === undefined || isNaN(val_acc)) { - throw new Error("Validation accuracy or loss is undefined or nan"); + async #evaluate( + dataset: Dataset, + ): Promise> { + const evaluation = await evaluate( + this.model, + dataset.map((t) => { + switch (t) { + case null: + case undefined: + throw new Error("nullish value in dataset"); + default: + // TODO unsafe cast + return t as { xs: tf.Tensor2D; ys: tf.Tensor3D }; } - structuredLogs.validation = { accuracy: logs.val_acc, loss: logs.val_loss} - } - yield structuredLogs - } + }), + this.config.maxEvalBatches, + ); + + return { + accuracy: evaluation.val_acc, + loss: evaluation.val_loss, + }; } - override predict (input: Sample): Promise { - const ret = this.model.predict(input) + override predict(input: Sample): Promise { + const ret = this.model.predict(input); if (Array.isArray(ret)) { - throw new Error('prediction yield many Tensors but should have only returned one') + throw new Error( + "prediction yield many Tensors but should have only returned one", + ); } - return Promise.resolve(ret) + return Promise.resolve(ret); } async generate(input: string, tokenizer: PreTrainedTokenizer, newTokens: number = 10): Promise { diff --git a/discojs/src/models/gpt/model.ts b/discojs/src/models/gpt/model.ts index 959468b57..113c7f798 100644 --- a/discojs/src/models/gpt/model.ts +++ b/discojs/src/models/gpt/model.ts @@ -57,6 +57,7 @@ class GPTModel extends tf.LayersModel { await callbacks.onTrainBegin?.() for (let epoch = 1; epoch <= trainingArgs.epochs; epoch++) { + let accuracyFraction: [number, number] = [0, 0]; let averageLoss = 0 let peakMemory = 0 let iteration = 1 @@ -69,18 +70,34 @@ class GPTModel extends tf.LayersModel { let weightUpdateTime = performance.now() await callbacks.onEpochBegin?.(epoch) const { xs, ys } = next.value as { xs: tf.Tensor2D, ys: tf.Tensor3D } - const lossFn: () => tf.Scalar = () => { + + // TODO include as a tensor inside the model + const accTensor = tf.tidy(() => { const logits = this.apply(xs) - if (Array.isArray(logits)) { + if (Array.isArray(logits)) throw new Error('model outputs too many tensor') - } - if (logits instanceof tf.SymbolicTensor) { + if (logits instanceof tf.SymbolicTensor) throw new Error('model outputs symbolic tensor') - } - return tf.losses.softmaxCrossEntropy(ys, logits) - } + return tf.metrics.categoricalAccuracy(ys, logits) + }) + const accSize = accTensor.shape.reduce((l, r) => l * r, 1) + const accSumTensor = accTensor.sum() + const accSum = await accSumTensor.array() + tf.dispose(accSumTensor) + if (typeof accSum !== 'number') + throw new Error('got multiple accuracy sum') + accuracyFraction = [accuracyFraction[0] + accSum, accuracyFraction[1] + accSize]; + tf.dispose([accTensor]) + const lossTensor = tf.tidy(() => { - const { grads, value: lossTensor } = this.optimizer.computeGradients(lossFn) + const { grads, value: lossTensor } = this.optimizer.computeGradients(() => { + const logits = this.apply(xs) + if (Array.isArray(logits)) + throw new Error('model outputs too many tensor') + if (logits instanceof tf.SymbolicTensor) + throw new Error('model outputs symbolic tensor') + return tf.losses.softmaxCrossEntropy(ys, logits) + }) const gradsClipped = clipByGlobalNormObj(grads, 1) this.optimizer.applyGradients(gradsClipped) return lossTensor @@ -89,6 +106,7 @@ class GPTModel extends tf.LayersModel { const loss = await lossTensor.array() averageLoss += loss weightUpdateTime = performance.now() - weightUpdateTime + tf.dispose([xs, ys, lossTensor]) if ( @@ -122,6 +140,7 @@ class GPTModel extends tf.LayersModel { } let logs: tf.Logs = { 'loss': averageLoss / iteration, + 'acc': accuracyFraction[0] / accuracyFraction[1], 'peakMemory': peakMemory } if (evalDataset !== undefined) { diff --git a/discojs/src/models/index.ts b/discojs/src/models/index.ts index aefb9b5fa..267e41bec 100644 --- a/discojs/src/models/index.ts +++ b/discojs/src/models/index.ts @@ -1,4 +1,5 @@ -export { EpochLogs, Model } from './model.js' +export { Model } from './model.js' +export { BatchLogs, EpochLogs, ValidationMetrics } from "./logs.js"; export { GPT } from './gpt/index.js' export { GPTConfig } from './gpt/config.js' diff --git a/discojs/src/models/logs.ts b/discojs/src/models/logs.ts new file mode 100644 index 000000000..dc7d92497 --- /dev/null +++ b/discojs/src/models/logs.ts @@ -0,0 +1,42 @@ +import { List } from "immutable"; + +export interface ValidationMetrics { + accuracy: number; + loss: number; +} + +export interface BatchLogs { + accuracy: number; + loss: number; + memoryUsage: number; // GB +} + +export class EpochLogs { + public readonly batches: List; + + constructor( + batches: Iterable, + public readonly validation?: ValidationMetrics, + ) { + this.batches = List(batches); + } + + get training(): Record<"accuracy" | "loss", number> { + const sum = this.batches.reduce( + (acc, batch) => ({ + accuracy: acc.accuracy + batch.accuracy, + loss: acc.loss + batch.loss, + }), + { loss: 0, accuracy: 0 }, + ); + + return { + accuracy: sum.accuracy / this.batches.size, + loss: sum.loss / this.batches.size, + }; + } + + get peakMemory(): number { + return this.batches.map((batch) => batch.memoryUsage).max() ?? 0; + } +} diff --git a/discojs/src/models/model.ts b/discojs/src/models/model.ts index 60b61764c..bf2f2ce8a 100644 --- a/discojs/src/models/model.ts +++ b/discojs/src/models/model.ts @@ -3,18 +3,7 @@ import type tf from "@tensorflow/tfjs"; import type { WeightsContainer } from "../index.js"; import type { Dataset } from "../dataset/index.js"; -export interface EpochLogs { - epoch: number; // first epoch is zero - training: { - loss: number, - accuracy?: number - }; - validation?: { - loss: number, - accuracy: number - }; - peakMemory: number; -} +import type { BatchLogs, EpochLogs } from "./logs.js"; // TODO still bound to tfjs export type Prediction = tf.Tensor; @@ -26,7 +15,7 @@ export type Sample = tf.Tensor; * Allow for various implementation of models (various train function, tensor-library, ...) **/ // TODO make it typesafe: same shape of data/input/weights -export abstract class Model implements Disposable{ +export abstract class Model implements Disposable { // TODO don't allow external access but upgrade train to return weights on every epoch /** Return training state */ abstract get weights(): WeightsContainer; @@ -45,14 +34,12 @@ export abstract class Model implements Disposable{ abstract train( trainingData: Dataset, validationData?: Dataset, - epochs?: number, - ): AsyncGenerator; + ): AsyncGenerator; /** Predict likely values */ // TODO extract in separated TrainedModel? abstract predict(input: Sample): Promise; - /** * This method is automatically called to cleanup the memory occupied by the model * when leaving the definition scope if the instance has been defined with the `using` keyword. diff --git a/discojs/src/models/tfjs.ts b/discojs/src/models/tfjs.ts index 8646c34cd..f653869dc 100644 --- a/discojs/src/models/tfjs.ts +++ b/discojs/src/models/tfjs.ts @@ -1,10 +1,12 @@ +import { List, Map } from 'immutable' import * as tf from '@tensorflow/tfjs' import { WeightsContainer } from '../index.js' +import type { Dataset } from '../dataset/index.js' +import { BatchLogs, EpochLogs } from './index.js' import { Model } from './index.js' -import type { EpochLogs, Prediction, Sample } from './model.js' -import type { Dataset } from '../dataset/index.js' +import type { Prediction, Sample } from './model.js' /** TensorFlow JavaScript model with standard training */ export class TFJS extends Model { @@ -30,52 +32,87 @@ export class TFJS extends Model { override async *train( trainingData: Dataset, validationData?: Dataset, - epochs = 1, - ): AsyncGenerator { - for (let epoch = 0; epoch < epochs; epoch++) { - let logs: tf.Logs | undefined; - let peakMemory = 0 - await this.model.fitDataset(trainingData, { - epochs: 1, - validationData, - callbacks: { - onBatchEnd: (_) => { - const currentMemory = tf.memory().numBytes / 1024 / 1024 / 1024 // GB - if (currentMemory > peakMemory) { - peakMemory = currentMemory - } - }, - onEpochEnd: (_, cur) => { logs = cur } + ): AsyncGenerator { + const batches = await trainingData.iterator(); // tf.LazyIterator isn't an AsyncGenerator + let batchesLogs = List(); + for (let batchNumber = 0; true; batchNumber++) { + const iteration = await batches.next(); + if (iteration.done) break; + const batch = iteration.value; + + const batchLogs = { + batch: batchNumber, + ...(await this.#runBatch(batch)), + }; + tf.dispose(batch); + + yield batchLogs; + batchesLogs = batchesLogs.push(batchLogs); + } + + const validation = validationData && (await this.#evaluate(validationData)); + return new EpochLogs(batchesLogs, validation); + } + + async #runBatch( + batch: tf.TensorContainer, + ): Promise> { + let logs: tf.Logs | undefined; + await this.model.fitDataset(tf.data.array([batch]), { + epochs: 1, + verbose: 0, // don't pollute + callbacks: { + onEpochEnd: (_, cur) => { + logs = cur; }, - }); + }, + }); + if (logs === undefined) throw new Error("batch didn't gave any logs"); + + const { loss, acc: accuracy } = logs; + if (loss === undefined || isNaN(loss)) + throw new Error("training loss is undefined or NaN"); + + return { + accuracy, + loss, + memoryUsage: tf.memory().numBytes / 1024 / 1024 / 1024, + }; + } - if (logs === undefined) { - throw new Error("Epoch didn't gave any logs"); - } - const { loss, acc, val_acc, val_loss } = logs; - if (loss === undefined || isNaN(loss) || acc === undefined || isNaN(acc)) { - throw new Error("Training loss is undefined or nan"); - } - const structuredLogs: EpochLogs = { - epoch, - peakMemory, - training: { - loss: logs.loss, - accuracy: logs.acc, - } - } - if (validationData !== undefined) { - if(val_loss === undefined || isNaN(val_loss) || - val_acc === undefined || isNaN(val_acc)) { - throw new Error("Invalid validation logs"); + async #evaluate( + dataset: Dataset, + ): Promise> { + const evaluation = await this.model.evaluateDataset( + dataset.map((t) => { + switch (t) { + case null: + case undefined: + throw new Error("nullish value in dataset"); + default: + return t as Exclude; } - structuredLogs.validation = { - accuracy: logs.val_acc, - loss: logs.val_loss - } - } - yield structuredLogs - } + }), + ); + const metricToValue = Map( + List(this.model.metricsNames).zip( + Array.isArray(evaluation) + ? List(await Promise.all(evaluation.map((t) => t.data()))) + : List.of(await evaluation.data()), + ), + ).map((values) => { + if (values.length !== 1) throw new Error("more than one metric value"); + return values[0]; + }); + + const [accuracy, loss] = [ + metricToValue.get("acc"), + metricToValue.get("loss"), + ]; + if (accuracy === undefined || loss === undefined) + throw new Error("some needed metrics are missing"); + + return { accuracy, loss }; } override predict (input: Sample): Promise { diff --git a/discojs/src/training/disco.ts b/discojs/src/training/disco.ts index d57baa4d3..fdcf12265 100644 --- a/discojs/src/training/disco.ts +++ b/discojs/src/training/disco.ts @@ -1,7 +1,10 @@ -import type { data, Logger, Memory, Task, TrainingInformation } from '../index.js' +import { List } from 'immutable' + +import { BatchLogs, data, EpochLogs, Logger, Memory, Task, TrainingInformation } from '../index.js' import { client as clients, EmptyMemory, ConsoleLogger } from '../index.js' import type { Aggregator } from '../aggregator/index.js' import { MeanAggregator } from '../aggregator/mean.js' +import { enumerate, split } from '../utils/async_iterator.js' import type { RoundLogs, Trainer } from './trainer/trainer.js' import { TrainerBuilder } from './trainer/trainer_builder.js' @@ -85,7 +88,14 @@ export class Disco { * @param dataTuple The data tuple */ // TODO RoundLogs should contain number of participants but Trainer doesn't need client - async *fit(dataTuple: data.DataSplit): AsyncGenerator { + async *fit( + dataTuple: data.DataSplit, + ): AsyncGenerator< + AsyncGenerator< + AsyncGenerator, + RoundLogs & { participants: number } + > + > { this.logger.success("Training started."); const trainData = dataTuple.train.preprocess().batch(); @@ -94,25 +104,39 @@ export class Disco { await this.client.connect(); const trainer = await this.trainer; - for await (const roundLogs of trainer.fitModel(trainData.dataset, validationData.dataset)) { - let msg = `Round: ${roundLogs.round}\n` - for (const epochLogs of roundLogs.epochs.values()) { - msg += ` Epoch: ${epochLogs.epoch}\n` - msg += ` Training loss: ${epochLogs.training.loss}\n` - if (epochLogs.training.accuracy !== undefined) { - msg += ` Training accuracy: ${epochLogs.training.accuracy}\n` - } - if (epochLogs.validation !== undefined) { - msg += ` Validation loss: ${epochLogs.validation.loss}\n` - msg += ` Validation accuracy: ${epochLogs.validation.accuracy}\n` + for await (const [round, epochs] of enumerate( + trainer.fitModel(trainData.dataset, validationData.dataset), + )) { + yield async function* (this: Disco) { + let epochsLogs = List(); + for await (const [epoch, batches] of enumerate(epochs)) { + const [gen, returnedEpochLogs] = split(batches); + + yield gen; + const epochLogs = await returnedEpochLogs; + epochsLogs = epochsLogs.push(epochLogs); + + this.logger.success( + [ + `Round: ${round}`, + ` Epoch: ${epoch}`, + ` Training loss: ${epochLogs.training.loss}`, + ` Training accuracy: ${epochLogs.training.accuracy}`, + epochLogs.validation !== undefined + ? ` Validation loss: ${epochLogs.validation.loss}` + : "", + epochLogs.validation !== undefined + ? ` Validation accuracy: ${epochLogs.validation.accuracy}` + : "", + ].join("\n"), + ); } - } - this.logger.success(msg) - yield { - ...roundLogs, - participants: this.client.nodes.size + 1 // add ourself - } + return { + epochs: epochsLogs, + participants: this.client.nodes.size + 1, // add ourself + }; + }.bind(this)(); } this.logger.success("Training finished."); diff --git a/discojs/src/training/trainer/trainer.ts b/discojs/src/training/trainer/trainer.ts index ed1f5bd5b..79ae937a1 100644 --- a/discojs/src/training/trainer/trainer.ts +++ b/discojs/src/training/trainer/trainer.ts @@ -2,11 +2,10 @@ import type tf from "@tensorflow/tfjs"; import { List } from "immutable"; import type { Model, Task } from "../../index.js"; - -import { EpochLogs } from "../../models/model.js"; +import * as async_iterator from "../../utils/async_iterator.js"; +import { BatchLogs, EpochLogs } from "../../models/index.js"; export interface RoundLogs { - round: number; epochs: List; } @@ -22,7 +21,10 @@ export abstract class Trainer { readonly #roundDuration: number; readonly #epochs: number; - private training?: AsyncGenerator; + private training?: AsyncGenerator< + AsyncGenerator, RoundLogs>, + void + >; constructor( task: Task, @@ -30,6 +32,9 @@ export abstract class Trainer { ) { this.#roundDuration = task.trainingInformation.roundDuration; this.#epochs = task.trainingInformation.epochs; + + if (!Number.isInteger(this.#epochs / this.#roundDuration)) + throw new Error(`round duration doesn't divide epochs`); } protected abstract onRoundBegin(round: number): Promise; @@ -49,34 +54,52 @@ export abstract class Trainer { async *fitModel( dataset: tf.data.Dataset, valDataset: tf.data.Dataset, - ): AsyncGenerator { - if (this.training !== undefined) { + ): AsyncGenerator< + AsyncGenerator, RoundLogs>, + void + > { + if (this.training !== undefined) throw new Error( "training already running, cancel it before launching a new one", ); - } - await this.onRoundBegin(0); + try { + this.training = this.#runRounds(dataset, valDataset); + yield* this.training; + } finally { + this.training = undefined; + } + } - this.training = this.model.train(dataset, valDataset, this.#epochs); + async *#runRounds( + dataset: tf.data.Dataset, + valDataset: tf.data.Dataset, + ): AsyncGenerator< + AsyncGenerator, RoundLogs>, + void + > { + const totalRound = Math.trunc(this.#epochs / this.#roundDuration); + for (let round = 0; round < totalRound; round++) { + await this.onRoundBegin(round); + yield this.#runRound(dataset, valDataset); + await this.onRoundEnd(round); + } + } - for await (const logs of this.training) { - // for now, round (sharing on network) == epoch (full pass over local data) - yield { - round: logs.epoch, - epochs: List.of(logs), - }; + async *#runRound( + dataset: tf.data.Dataset, + valDataset: tf.data.Dataset, + ): AsyncGenerator, RoundLogs> { + let epochsLogs = List(); + for (let epoch = 0; epoch < this.#roundDuration; epoch++) { + const [gen, epochLogs] = async_iterator.split( + this.model.train(dataset, valDataset), + ); - if (logs.epoch % this.#roundDuration === 0) { - const round = Math.trunc(logs.epoch / this.#roundDuration); - await this.onRoundEnd(round); - await this.onRoundBegin(round); - } + yield gen; + epochsLogs = epochsLogs.push(await epochLogs); } - const round = Math.trunc(this.#epochs / this.#roundDuration); - await this.onRoundEnd(round); - - this.training = undefined; + return { epochs: epochsLogs }; } } diff --git a/discojs/src/utils/async_iterator.spec.ts b/discojs/src/utils/async_iterator.spec.ts new file mode 100644 index 000000000..af4a34602 --- /dev/null +++ b/discojs/src/utils/async_iterator.spec.ts @@ -0,0 +1,63 @@ +import { expect } from "chai"; + +import { split, gather } from "./async_iterator.js"; + +// Array.fromAsync not yet widely used (2024) +async function arrayFromAsync(iter: AsyncIterable): Promise { + const ret: T[] = []; + for await (const e of iter) ret.push(e); + return ret; +} + +describe("gather", () => { + it("returns generator value", async () => { + const [yielded, returned] = await gather( + // eslint-disable-next-line @typescript-eslint/require-await + (async function* () { + yield "yield"; + return "return"; + })(), + ); + + expect(yielded.toArray()).to.have.same.ordered.members(["yield"]); + expect(returned).to.equals("return"); + }); +}); + +describe("split", () => { + it("returns both iterator and return value", async () => { + const [gen, ret] = split( + // eslint-disable-next-line @typescript-eslint/require-await + (async function* () { + yield "yield"; + return "return"; + })(), + ); + + expect(await arrayFromAsync(gen)).to.have.same.ordered.members(["yield"]); + expect(await ret).to.equals("return"); + }); + + it("throws returned when iterator throws", async () => { + const [gen, ret] = split( + // eslint-disable-next-line require-yield, @typescript-eslint/require-await + (async function* () { + throw new Error(); + })(), + ); + + try { + for await (const _ of gen); + } catch { + // expected + } + + try { + await ret; + } catch { + return; // all good + } + + expect(false, "should have thrown").to.be.true; + }); +}); diff --git a/discojs/src/utils/async_iterator.ts b/discojs/src/utils/async_iterator.ts new file mode 100644 index 000000000..0a5e6e676 --- /dev/null +++ b/discojs/src/utils/async_iterator.ts @@ -0,0 +1,79 @@ +import { List } from "immutable"; + +// `Promise.withResolvers` not widely deployed +function PromiseWithResolvers(): [ + Promise, + (_: T) => void, + (_: unknown) => void, +] { + let resolve: (_: T) => void, reject: (_: unknown) => void; + resolve = reject = () => { + // should not happen as Promise are run on creation + throw new Error("race condition triggered"); + }; + + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + return [promise, resolve, reject]; +} + +/** + * Split yields from return value + * + * You need to consume the iterator to resolve the returned value + **/ +export function split( + iter: AsyncIterator, +): [AsyncGenerator, Promise] { + const [returnPromise, returnResolve, returnReject] = + PromiseWithResolvers(); + + return [ + (async function* () { + try { + while (true) { + const v = await iter.next(); + if (!v.done) { + yield v.value; + continue; + } + + returnResolve(v.value); + return v.value; + } + } catch (e) { + returnReject(e); + throw e; + } + })(), + returnPromise, + ]; +} + +/** Zip iterator with a infinite counter */ +export function enumerate( + iter: AsyncIterator | Iterator, +): AsyncGenerator<[number, T], U> { + return (async function* () { + for (let i = 0; ; i++) { + const v = await iter.next(); + if (v.done) return v.value; + yield [i, v.value]; + } + })(); +} + +/** Run the whole iterator to get yielded & returned */ +export async function gather( + iter: AsyncIterator, +): Promise<[List, U]> { + let elems = List(); + for (;;) { + const v = await iter.next(); + if (v.done) return [elems, v.value]; + elems = elems.push(v.value); + } +} diff --git a/docs/examples/README.md b/docs/examples/README.md index 2abbc7df8..9d5b0cffc 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -22,7 +22,9 @@ As you can see in `training.ts` a client is represented by a `Disco` object: ```js const disco = new Disco(task, { url, scheme: "federated" }); -await disco.fit(dataset); // Start training on the dataset +for await (const round of disco.fit(dataset)) + for await (const epoch of round) + for await (const batch of epoch); await disco.close(); ``` diff --git a/docs/examples/training.ts b/docs/examples/training.ts index 585685368..d2ab3936d 100644 --- a/docs/examples/training.ts +++ b/docs/examples/training.ts @@ -12,9 +12,13 @@ import { startServer } from 'server' async function runUser (url: URL, task: Task, dataset: data.DataSplit): Promise { // Create Disco object associated with the server url, the training scheme const disco = new Disco(task, { url, scheme: 'federated' }) - for await (const _ of disco.fit(dataset)); // Start training on the dataset - // Stop training and disconnect from the remote server + // Run training on the dataset + for await (const round of disco.fit(dataset)) + for await (const epoch of round) + for await (const _ of epoch); + + // Disconnect from the remote server await disco.close() } diff --git a/docs/examples/wikitext.ts b/docs/examples/wikitext.ts index b3b15e6ae..5367a9180 100644 --- a/docs/examples/wikitext.ts +++ b/docs/examples/wikitext.ts @@ -29,7 +29,9 @@ async function main(): Promise { const aggregator = new aggregators.MeanAggregator() const client = new clients.federated.FederatedClient(url, task, aggregator) const disco = new Disco(task, { scheme: 'federated', client, aggregator }) - for await (const _ of disco.fit(dataset)); + for await (const round of disco.fit(dataset)) + for await (const epoch of round) + for await (const _ of epoch); // Get the model and complete the prompt if (aggregator.model === undefined) { diff --git a/server/tests/e2e/federated.spec.ts b/server/tests/e2e/federated.spec.ts index bbe789da4..63255a96a 100644 --- a/server/tests/e2e/federated.spec.ts +++ b/server/tests/e2e/federated.spec.ts @@ -7,7 +7,8 @@ import { assert, expect } from 'chai' import type { RoundLogs, WeightsContainer } from '@epfml/discojs' import { Disco, client as clients, data, - aggregator as aggregators, defaultTasks + aggregator as aggregators, defaultTasks, + async_iterator } from '@epfml/discojs' import { NodeImageLoader, NodeTabularLoader, NodeTextLoader } from '@epfml/discojs-node' @@ -52,7 +53,7 @@ describe("end-to-end federated", function () { const files = [DATASET_DIR + 'titanic_train.csv'] const titanicTask = defaultTasks.titanic.getTask() - titanicTask.trainingInformation.epochs = 5 + titanicTask.trainingInformation.epochs = titanicTask.trainingInformation.roundDuration = 5 const data = await (new NodeTabularLoader(titanicTask, ',').loadAll( files, { @@ -68,7 +69,10 @@ describe("end-to-end federated", function () { let logs = List() for await (const round of disco.fit(data)) { - logs = logs.push(round) + const [roundGen, roundLogs] = async_iterator.split(round) + for await (const epoch of roundGen) + for await (const _ of epoch); + logs = logs.push(await roundLogs) } await disco.close() @@ -99,7 +103,10 @@ describe("end-to-end federated", function () { let logs = List() for await (const round of disco.fit(dataSplit)) { - logs = logs.push(round) + const [roundGen, roundLogs] = async_iterator.split(round) + for await (const epoch of roundGen) + for await (const _ of epoch); + logs = logs.push(await roundLogs) } await disco.close() @@ -120,7 +127,8 @@ describe("end-to-end federated", function () { const negativeLabels = files[1].map(_ => 'COVID-Negative') const labels = positiveLabels.concat(negativeLabels) const lusCovidTask = defaultTasks.lusCovid.getTask() - lusCovidTask.trainingInformation.epochs = 15 + lusCovidTask.trainingInformation.epochs = 16 + lusCovidTask.trainingInformation.roundDuration = 4 const data = await new NodeImageLoader(lusCovidTask) .loadAll(files.flat(), { labels, channels: 3 }) @@ -131,7 +139,10 @@ describe("end-to-end federated", function () { let logs = List() for await (const round of disco.fit(data)) { - logs = logs.push(round) + const [roundGen, roundLogs] = async_iterator.split(round) + for await (const epoch of roundGen) + for await (const _ of epoch); + logs = logs.push(await roundLogs) } await disco.close() diff --git a/webapp/cypress/e2e/library.cy.ts b/webapp/cypress/e2e/library.cy.ts index ea4b204c0..851620d18 100644 --- a/webapp/cypress/e2e/library.cy.ts +++ b/webapp/cypress/e2e/library.cy.ts @@ -19,7 +19,7 @@ describe("model library", () => { function setupForTask(taskProvider: TaskProvider): void { cy.intercept({ hostname: "server", pathname: "/tasks" }, (req) => { const task = taskProvider.getTask(); - task.trainingInformation.epochs = 3; + task.trainingInformation.epochs = task.trainingInformation.roundDuration = 3; req.reply([task]); }); @@ -105,7 +105,7 @@ describe("model library", () => { cy.contains("button", "train alone").click(); cy.contains("h6", "current round") .next({ timeout: 10_000 }) - .should("have.text", "3"); + .should("have.text", "1"); cy.contains("button", "next").click(); // TODO do not save by default, only via "save model" button diff --git a/webapp/cypress/e2e/training.cy.ts b/webapp/cypress/e2e/training.cy.ts index fa4df1338..484720558 100644 --- a/webapp/cypress/e2e/training.cy.ts +++ b/webapp/cypress/e2e/training.cy.ts @@ -57,8 +57,8 @@ describe("training page", () => { cy.contains("button", "train alone").click(); cy.contains("h6", "current round") - .next({ timeout: 30_000 }) - .should("have.text", "20"); + .next({ timeout: 40_000 }) + .should("have.text", "2"); cy.contains("button", "next").click(); cy.contains("button", "test model").click(); diff --git a/webapp/src/charts.ts b/webapp/src/charts.ts deleted file mode 100644 index 4e83049e3..000000000 --- a/webapp/src/charts.ts +++ /dev/null @@ -1,73 +0,0 @@ -const chartOptions = { - chart: { - id: 'realtime', - width: 'auto', - height: 'auto', - // type: 'area', - animations: { - enabled: true, - easing: 'linear', - dynamicAnimation: { - speed: 1000 - } - }, - toolbar: { - show: false - }, - zoom: { - enabled: false - } - }, - dataLabels: { - enabled: false - }, - colors: [ - '#6096BA' - ], - fill: { - colors: ['#E2E8F0'], - type: 'solid', - opacity: 0.6 - }, - stroke: { - curve: 'smooth' - }, - markers: { - size: 0.5 - }, - grid: { - xaxis: { - lines: { - show: false - } - }, - yaxis: { - lines: { - show: false - } - } - }, - yaxis: { - max: 100, - min: 0, - labels: { - show: true, - formatter: function (value: number) { - return value.toFixed(0); - } - } - }, - xaxis: { - labels: { - show: false - } - }, - legend: { - show: false - }, - tooltip: { - enabled: true - } -} - -export { chartOptions } diff --git a/webapp/src/components/training/Trainer.vue b/webapp/src/components/training/Trainer.vue index 1f7099980..5ad48e7b6 100644 --- a/webapp/src/components/training/Trainer.vue +++ b/webapp/src/components/training/Trainer.vue @@ -30,7 +30,9 @@
@@ -42,12 +44,8 @@ import { List } from "immutable"; import { ref, computed } from "vue"; -import type { RoundLogs, Task } from "@epfml/discojs"; -import { - data, - EmptyMemory, - Disco, -} from "@epfml/discojs"; +import type { BatchLogs, EpochLogs, RoundLogs, Task } from "@epfml/discojs"; +import { async_iterator, data, EmptyMemory, Disco } from "@epfml/discojs"; import { IndexedDB } from "@epfml/discojs-web"; import { getClient } from '@/clients' @@ -69,19 +67,40 @@ const props = defineProps<{ const displayModelCaching = ref(true) const trainingGenerator = - ref>(); -const logs = ref(List()); + ref< + AsyncGenerator< + AsyncGenerator< + AsyncGenerator, + RoundLogs & { participants: number } + > + > + >(); +const roundGenerator = + ref< + AsyncGenerator< + AsyncGenerator, + RoundLogs & { participants: number } + > + >(); +const epochGenerator = ref>(); +const roundsLogs = ref(List()); +const epochsOfRoundLogs = ref(List()); +const batchesOfEpochLogs = ref(List()); const messages = ref(List()); const hasValidationData = computed( () => props.task.trainingInformation.validationSplit > 0, ); +const stopper = new Error("stop training") + async function startTraining(distributed: boolean): Promise { // Reset training information before starting a new training trainingGenerator.value = undefined - logs.value = List() - messages.value = List() + roundsLogs.value = List() + epochsOfRoundLogs.value = List() + batchesOfEpochLogs.value = List() + messages.value = List() let dataset: data.DataSplit; try { @@ -130,16 +149,29 @@ async function startTraining(distributed: boolean): Promise { try { displayModelCaching.value = false // hide model caching buttons during training trainingGenerator.value = disco.fit(dataset); - logs.value = List(); - for await (const roundLogs of trainingGenerator.value) - logs.value = logs.value.push(roundLogs); - if (trainingGenerator.value === undefined) { - toaster.info("Training stopped"); - return; + roundsLogs.value = List() + for await (const round of trainingGenerator.value) { + const [roundGen, roundLogs] = async_iterator.split(round) + + roundGenerator.value = roundGen + epochsOfRoundLogs.value = List() + for await (const epoch of roundGenerator.value) { + const [epochGen, epochLogs] = async_iterator.split(epoch) + + epochGenerator.value = epochGen + batchesOfEpochLogs.value = List() + for await (const batch of epochGenerator.value) + batchesOfEpochLogs.value = batchesOfEpochLogs.value.push(batch); + epochsOfRoundLogs.value = epochsOfRoundLogs.value.push(await epochLogs) + } + roundsLogs.value = roundsLogs.value.push(await roundLogs) } } catch (e) { - if (e instanceof Error && e.message.includes("greater than WebGL maximum on this browser")) { + if (e === stopper) { + toaster.info("Training stopped"); + return + } else if (e instanceof Error && e.message.includes("greater than WebGL maximum on this browser")) { toaster.error("Unfortunately your browser doesn't support training this task.
If you are on Firefox try using Chrome instead.") } else if (e instanceof Error && e.message.includes("loss is undefined or nan")) { toaster.error("Training is not converging. Data potentially needs better preprocessing.") @@ -157,10 +189,13 @@ async function startTraining(distributed: boolean): Promise { } async function stopTraining(): Promise { - const generator = trainingGenerator.value; - if (generator === undefined) return; - + trainingGenerator.value?.throw(stopper); trainingGenerator.value = undefined; - generator.return(); + + roundGenerator.value?.throw(stopper); + roundGenerator.value = undefined; + + epochGenerator.value?.throw(stopper); + epochGenerator.value = undefined; } diff --git a/webapp/src/components/training/TrainingInformation.vue b/webapp/src/components/training/TrainingInformation.vue index 04b19b852..1ca56d9cc 100644 --- a/webapp/src/components/training/TrainingInformation.vue +++ b/webapp/src/components/training/TrainingInformation.vue @@ -4,7 +4,21 @@
+ + + + + + @@ -34,12 +48,10 @@ - + -
    +
    • Data stays private and offline
    • Code-free and installation-free
    • From b90b1937bb03d29228bdd062daa7eb76243d672a Mon Sep 17 00:00:00 2001 From: tharvik Date: Tue, 2 Jul 2024 15:01:59 +0200 Subject: [PATCH 18/18] webapp/trainer: avoid graph shakes --- webapp/src/components/training/Trainer.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/training/Trainer.vue b/webapp/src/components/training/Trainer.vue index aa09d3960..e8d41fe98 100644 --- a/webapp/src/components/training/Trainer.vue +++ b/webapp/src/components/training/Trainer.vue @@ -155,17 +155,19 @@ async function startTraining(distributed: boolean): Promise { const [roundGen, roundLogs] = async_iterator.split(round) roundGenerator.value = roundGen - epochsOfRoundLogs.value = List() for await (const epoch of roundGenerator.value) { const [epochGen, epochLogs] = async_iterator.split(epoch) epochGenerator.value = epochGen - batchesOfEpochLogs.value = List() for await (const batch of epochGenerator.value) batchesOfEpochLogs.value = batchesOfEpochLogs.value.push(batch); + epochsOfRoundLogs.value = epochsOfRoundLogs.value.push(await epochLogs) + batchesOfEpochLogs.value = List() } + roundsLogs.value = roundsLogs.value.push(await roundLogs) + epochsOfRoundLogs.value = List() } } catch (e) { if (e === stopper) {