diff --git a/package-lock.json b/package-lock.json index b3441a2f..e513bf8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,13 @@ "vitest": "^4.0.16" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.30", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz", + "integrity": "sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==", + "dev": true, + "license": "MIT" + }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", @@ -538,9 +545,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.22.tgz", - "integrity": "sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==", + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.23.tgz", + "integrity": "sha512-YEmgyklR6l/oKUltidNVYdjSmLSW88vMsKx0pmiS3r71s8ZZRpd8A0Yf0U+6p/RzElmMnPBv27hNWjDQMSZRtQ==", "dev": true, "funding": [ { @@ -588,9 +595,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -605,9 +612,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -622,9 +629,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -639,9 +646,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -656,9 +663,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -673,9 +680,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -690,9 +697,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -707,9 +714,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -724,9 +731,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -741,9 +748,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -758,9 +765,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -775,9 +782,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -792,9 +799,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -809,9 +816,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -826,9 +833,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -843,9 +850,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -860,9 +867,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -877,9 +884,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -894,9 +901,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -911,9 +918,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -928,9 +935,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -945,9 +952,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -962,9 +969,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -979,9 +986,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -996,9 +1003,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -1013,9 +1020,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -1029,6 +1036,24 @@ "node": ">=18" } }, + "node_modules/@exodus/bytes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.8.0.tgz", + "integrity": "sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@exodus/crypto": "^1.0.0-rc.4" + }, + "peerDependenciesMeta": { + "@exodus/crypto": { + "optional": true + } + } + }, "node_modules/@google/genai": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.30.0.tgz", @@ -1730,9 +1755,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", - "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", "cpu": [ "arm" ], @@ -1744,9 +1769,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", - "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", "cpu": [ "arm64" ], @@ -1758,9 +1783,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", - "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", "cpu": [ "arm64" ], @@ -1772,9 +1797,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", - "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", "cpu": [ "x64" ], @@ -1786,9 +1811,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", - "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", "cpu": [ "arm64" ], @@ -1800,9 +1825,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", - "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", "cpu": [ "x64" ], @@ -1814,9 +1839,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", - "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", "cpu": [ "arm" ], @@ -1828,9 +1853,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", - "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", "cpu": [ "arm" ], @@ -1842,9 +1867,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", - "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", "cpu": [ "arm64" ], @@ -1856,9 +1881,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", - "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", "cpu": [ "arm64" ], @@ -1870,9 +1895,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", - "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", "cpu": [ "loong64" ], @@ -1884,9 +1923,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", - "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", "cpu": [ "ppc64" ], @@ -1898,9 +1951,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", - "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", "cpu": [ "riscv64" ], @@ -1912,9 +1965,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", - "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", "cpu": [ "riscv64" ], @@ -1926,9 +1979,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", - "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", "cpu": [ "s390x" ], @@ -1940,9 +1993,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", - "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", "cpu": [ "x64" ], @@ -1954,9 +2007,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", - "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", "cpu": [ "x64" ], @@ -1967,10 +2020,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", - "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", "cpu": [ "arm64" ], @@ -1982,9 +2049,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", - "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", "cpu": [ "arm64" ], @@ -1996,9 +2063,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", - "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", "cpu": [ "ia32" ], @@ -2010,9 +2077,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", - "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", "cpu": [ "x64" ], @@ -2024,9 +2091,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", - "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", "cpu": [ "x64" ], @@ -2498,7 +2565,6 @@ "@types/d3-selection": "*" } }, -<<<<<<< HEAD "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -2512,7 +2578,7 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" -======= + }, "node_modules/@types/jszip": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@types/jszip/-/jszip-3.4.0.tgz", @@ -2522,7 +2588,6 @@ "dependencies": { "jszip": "*" } ->>>>>>> develop }, "node_modules/@types/node": { "version": "24.10.1", @@ -3057,18 +3122,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, -<<<<<<< HEAD "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, -======= + "license": "MIT" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", ->>>>>>> develop "license": "MIT" }, "node_modules/cross-spawn": { @@ -3107,9 +3171,9 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.6.tgz", - "integrity": "sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3378,9 +3442,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3391,32 +3455,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -3667,16 +3731,16 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/html-escaper": { @@ -3713,19 +3777,11 @@ "node": ">= 14" } }, -<<<<<<< HEAD - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" }, "node_modules/indent-string": { "version": "4.0.0", @@ -3736,19 +3792,12 @@ "engines": { "node": ">=8" } -======= - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" ->>>>>>> develop }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", @@ -3759,18 +3808,17 @@ "node": ">=8" } }, -<<<<<<< HEAD "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, -======= + "license": "MIT" + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", ->>>>>>> develop "license": "MIT" }, "node_modules/isexe": { @@ -3886,35 +3934,35 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz", - "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/dom-selector": "^6.7.2", - "cssstyle": "^5.3.1", + "@acemir/cssom": "^0.9.28", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", + "cssstyle": "^5.3.4", "data-urls": "^6.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^4.0.0", + "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", - "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.0", - "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.1.0", "ws": "^8.18.3", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -3947,7 +3995,6 @@ "bignumber.js": "^9.0.0" } }, -<<<<<<< HEAD "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3959,7 +4006,8 @@ }, "engines": { "node": ">=6" -======= + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -3970,7 +4018,6 @@ "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" ->>>>>>> develop } }, "node_modules/jwa": { @@ -4013,8 +4060,6 @@ } ], "license": "MIT" -<<<<<<< HEAD -======= }, "node_modules/lie": { "version": "3.3.0", @@ -4024,7 +4069,6 @@ "dependencies": { "immediate": "~3.0.5" } ->>>>>>> develop }, "node_modules/lightningcss": { "version": "1.30.2", @@ -4544,7 +4588,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, -<<<<<<< HEAD + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", @@ -4557,13 +4606,6 @@ "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } -======= - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" ->>>>>>> develop }, "node_modules/path-key": { "version": "3.1.1", @@ -4650,7 +4692,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, -<<<<<<< HEAD "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -4692,6 +4733,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4701,13 +4748,6 @@ "engines": { "node": ">=6" } -======= - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" ->>>>>>> develop }, "node_modules/react": { "version": "19.2.0", @@ -4784,7 +4824,6 @@ "react": "^19.2.0" } }, -<<<<<<< HEAD "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -4795,6 +4834,27 @@ "node": ">=0.10.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -4818,28 +4878,6 @@ "engines": { "node": ">=0.10.0" } -======= - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" ->>>>>>> develop }, "node_modules/rimraf": { "version": "5.0.10", @@ -4857,9 +4895,9 @@ } }, "node_modules/rollup": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", - "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", "dev": true, "license": "MIT", "dependencies": { @@ -4873,38 +4911,34 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.54.0", - "@rollup/rollup-android-arm64": "4.54.0", - "@rollup/rollup-darwin-arm64": "4.54.0", - "@rollup/rollup-darwin-x64": "4.54.0", - "@rollup/rollup-freebsd-arm64": "4.54.0", - "@rollup/rollup-freebsd-x64": "4.54.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", - "@rollup/rollup-linux-arm-musleabihf": "4.54.0", - "@rollup/rollup-linux-arm64-gnu": "4.54.0", - "@rollup/rollup-linux-arm64-musl": "4.54.0", - "@rollup/rollup-linux-loong64-gnu": "4.54.0", - "@rollup/rollup-linux-ppc64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-musl": "4.54.0", - "@rollup/rollup-linux-s390x-gnu": "4.54.0", - "@rollup/rollup-linux-x64-gnu": "4.54.0", - "@rollup/rollup-linux-x64-musl": "4.54.0", - "@rollup/rollup-openharmony-arm64": "4.54.0", - "@rollup/rollup-win32-arm64-msvc": "4.54.0", - "@rollup/rollup-win32-ia32-msvc": "4.54.0", - "@rollup/rollup-win32-x64-gnu": "4.54.0", - "@rollup/rollup-win32-x64-msvc": "4.54.0", + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" } }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4925,13 +4959,6 @@ ], "license": "MIT" }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -5064,7 +5091,6 @@ "node": ">=0.10.0" } }, -<<<<<<< HEAD "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -5077,7 +5103,8 @@ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, -======= + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -5091,7 +5118,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", ->>>>>>> develop "license": "MIT" }, "node_modules/string-width": { @@ -5421,26 +5447,31 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, -<<<<<<< HEAD + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5449,14 +5480,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -5587,13 +5618,6 @@ "engines": { "node": ">=18" } -======= - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" ->>>>>>> develop }, "node_modules/web-streams-polyfill": { "version": "3.3.3", @@ -5605,29 +5629,15 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=20" } }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", diff --git a/src/app/api/workflow-images/route.ts b/src/app/api/workflow-images/route.ts new file mode 100644 index 00000000..5168f8d3 --- /dev/null +++ b/src/app/api/workflow-images/route.ts @@ -0,0 +1,230 @@ +import { NextRequest, NextResponse } from "next/server"; +import * as fs from "fs/promises"; +import * as path from "path"; +import { logger } from "@/utils/logger"; + +export const maxDuration = 300; // 5 minute timeout for large image operations + +const IMAGES_FOLDER = "inputs"; +const LEGACY_IMAGES_FOLDER = ".images"; // For backward compatibility + +// POST: Save an image to the workflow's inputs or generations folder +export async function POST(request: NextRequest) { + let workflowPath: string | undefined; + let imageId: string | undefined; + let folder: string | undefined; + try { + const body = await request.json(); + workflowPath = body.workflowPath; + imageId = body.imageId; + folder = body.folder || IMAGES_FOLDER; // Default to "inputs" + const imageData = body.imageData; // Base64 data URL + + // Validate folder is one of the allowed values + if (folder !== IMAGES_FOLDER && folder !== "generations") { + folder = IMAGES_FOLDER; + } + + logger.info('file.save', 'Workflow image save request received', { + workflowPath, + imageId, + folder, + hasImageData: !!imageData, + }); + + if (!workflowPath || !imageId || !imageData) { + logger.warn('file.save', 'Workflow image save validation failed: missing fields', { + hasWorkflowPath: !!workflowPath, + hasImageId: !!imageId, + hasImageData: !!imageData, + }); + return NextResponse.json( + { success: false, error: "Missing required fields (workflowPath, imageId, imageData)" }, + { status: 400 } + ); + } + + // Validate workflow directory exists + try { + const stats = await fs.stat(workflowPath); + if (!stats.isDirectory()) { + logger.warn('file.error', 'Workflow image save failed: path is not a directory', { + workflowPath, + }); + return NextResponse.json( + { success: false, error: "Workflow path is not a directory" }, + { status: 400 } + ); + } + } catch (dirError) { + logger.warn('file.error', 'Workflow image save failed: directory does not exist', { + workflowPath, + }); + return NextResponse.json( + { success: false, error: "Workflow directory does not exist" }, + { status: 400 } + ); + } + + // Create target folder if it doesn't exist + const targetFolder = path.join(workflowPath, folder); + try { + await fs.mkdir(targetFolder, { recursive: true }); + } catch (mkdirError) { + logger.error('file.error', 'Failed to create target folder', { + targetFolder, + }, mkdirError instanceof Error ? mkdirError : undefined); + return NextResponse.json( + { success: false, error: "Failed to create target folder" }, + { status: 500 } + ); + } + + // Construct file path + const filename = `${imageId}.png`; + const filePath = path.join(targetFolder, filename); + + // Extract base64 data and convert to buffer + const base64Data = imageData.replace(/^data:image\/\w+;base64,/, ""); + const buffer = Buffer.from(base64Data, "base64"); + + // Write the image file + await fs.writeFile(filePath, buffer); + + logger.info('file.save', 'Workflow image saved successfully', { + filePath, + imageId, + fileSize: buffer.length, + }); + + return NextResponse.json({ + success: true, + imageId, + filePath, + }); + } catch (error) { + logger.error('file.error', 'Failed to save workflow image', { + workflowPath, + imageId, + }, error instanceof Error ? error : undefined); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : "Save failed", + }, + { status: 500 } + ); + } +} + +// GET: Load an image from the workflow's folders (inputs, generations, or legacy .images) +export async function GET(request: NextRequest) { + const workflowPath = request.nextUrl.searchParams.get("workflowPath"); + const imageId = request.nextUrl.searchParams.get("imageId"); + const folder = request.nextUrl.searchParams.get("folder"); // Optional hint for which folder to check first + + logger.info('file.load', 'Workflow image load request received', { + workflowPath, + imageId, + folder, + }); + + if (!workflowPath || !imageId) { + logger.warn('file.load', 'Workflow image load validation failed: missing parameters', { + hasWorkflowPath: !!workflowPath, + hasImageId: !!imageId, + }); + return NextResponse.json( + { success: false, error: "Missing required parameters (workflowPath, imageId)" }, + { status: 400 } + ); + } + + try { + // Validate workflow directory exists + try { + const stats = await fs.stat(workflowPath); + if (!stats.isDirectory()) { + return NextResponse.json( + { success: false, error: "Workflow path is not a directory" }, + { status: 400 } + ); + } + } catch { + return NextResponse.json( + { success: false, error: "Workflow directory does not exist" }, + { status: 400 } + ); + } + + // Construct file path - check folders in order based on hint + const filename = `${imageId}.png`; + const inputsFolder = path.join(workflowPath, IMAGES_FOLDER); + const generationsFolder = path.join(workflowPath, "generations"); + const legacyFolder = path.join(workflowPath, LEGACY_IMAGES_FOLDER); + + // Build search order based on folder hint + const searchOrder = folder === "generations" + ? [generationsFolder, inputsFolder, legacyFolder] + : [inputsFolder, generationsFolder, legacyFolder]; + + let filePath: string | null = null; + + // Check each folder in order + for (const searchFolder of searchOrder) { + const candidatePath = path.join(searchFolder, filename); + try { + await fs.access(candidatePath); + filePath = candidatePath; + if (searchFolder === legacyFolder) { + logger.info('file.load', 'Found image in legacy .images folder', { filePath }); + } + break; + } catch { + // File not found in this folder, try next + } + } + + if (!filePath) { + logger.warn('file.error', 'Workflow image load failed: file not found', { + imageId, + searchedFolders: searchOrder, + }); + return NextResponse.json( + { success: false, error: "Image file not found" }, + { status: 404 } + ); + } + + // Read the image file + const buffer = await fs.readFile(filePath); + + // Convert to base64 data URL + const base64 = buffer.toString("base64"); + const dataUrl = `data:image/png;base64,${base64}`; + + logger.info('file.load', 'Workflow image loaded successfully', { + filePath, + imageId, + fileSize: buffer.length, + }); + + return NextResponse.json({ + success: true, + imageId, + image: dataUrl, + }); + } catch (error) { + logger.error('file.error', 'Failed to load workflow image', { + workflowPath, + imageId, + }, error instanceof Error ? error : undefined); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : "Load failed", + }, + { status: 500 } + ); + } +} diff --git a/src/app/api/workflow/route.ts b/src/app/api/workflow/route.ts index b638d887..cb8ed2d6 100644 --- a/src/app/api/workflow/route.ts +++ b/src/app/api/workflow/route.ts @@ -57,6 +57,22 @@ export async function POST(request: NextRequest) { ); } + // Auto-create subfolders for inputs and generations + const inputsFolder = path.join(directoryPath, "inputs"); + const generationsFolder = path.join(directoryPath, "generations"); + + try { + await fs.mkdir(inputsFolder, { recursive: true }); + await fs.mkdir(generationsFolder, { recursive: true }); + } catch (mkdirError) { + logger.warn('file.save', 'Failed to create subfolders (non-fatal)', { + inputsFolder, + generationsFolder, + error: mkdirError instanceof Error ? mkdirError.message : 'Unknown error', + }); + // Continue anyway - folders may already exist or be created later + } + // Sanitize filename (remove special chars, ensure .json extension) const safeName = filename.replace(/[^a-zA-Z0-9-_]/g, "_"); const filePath = path.join(directoryPath, `${safeName}.json`); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index a5fe1dd9..81336d2e 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -51,11 +51,11 @@ export function Header() { if (!file) return; const reader = new FileReader(); - reader.onload = (event) => { + reader.onload = async (event) => { try { const workflow = JSON.parse(event.target?.result as string) as WorkflowFile; if (workflow.version && workflow.nodes && workflow.edges) { - loadWorkflow(workflow); + await loadWorkflow(workflow); } else { alert("Invalid workflow file format"); } @@ -69,8 +69,8 @@ export function Header() { e.target.value = ""; }; - const handleProjectSave = async (id: string, name: string, path: string, genPath: string | null) => { - setWorkflowMetadata(id, name, path, genPath); + const handleProjectSave = async (id: string, name: string, path: string) => { + setWorkflowMetadata(id, name, path); // generationsPath is auto-derived setShowProjectModal(false); // Small delay to let state update setTimeout(() => { diff --git a/src/components/ProjectSetupModal.tsx b/src/components/ProjectSetupModal.tsx index 9a18f169..b966ee70 100644 --- a/src/components/ProjectSetupModal.tsx +++ b/src/components/ProjectSetupModal.tsx @@ -6,7 +6,7 @@ import { generateWorkflowId, useWorkflowStore } from "@/store/workflowStore"; interface ProjectSetupModalProps { isOpen: boolean; onClose: () => void; - onSave: (id: string, name: string, directoryPath: string, generationsPath: string | null) => void; + onSave: (id: string, name: string, directoryPath: string) => void; mode: "new" | "settings"; } @@ -16,14 +16,13 @@ export function ProjectSetupModal({ onSave, mode, }: ProjectSetupModalProps) { - const { workflowName, saveDirectoryPath, generationsPath } = useWorkflowStore(); + const { workflowName, saveDirectoryPath, useExternalImageStorage, setUseExternalImageStorage } = useWorkflowStore(); const [name, setName] = useState(""); const [directoryPath, setDirectoryPath] = useState(""); - const [genPath, setGenPath] = useState(""); + const [externalStorage, setExternalStorage] = useState(true); const [isValidating, setIsValidating] = useState(false); - const [isBrowsingWorkflow, setIsBrowsingWorkflow] = useState(false); - const [isBrowsingGen, setIsBrowsingGen] = useState(false); + const [isBrowsing, setIsBrowsing] = useState(false); const [error, setError] = useState(null); // Pre-fill when opening in settings mode @@ -31,18 +30,15 @@ export function ProjectSetupModal({ if (isOpen && mode === "settings") { setName(workflowName || ""); setDirectoryPath(saveDirectoryPath || ""); - setGenPath(generationsPath || ""); + setExternalStorage(useExternalImageStorage); } else if (isOpen && mode === "new") { setName(""); setDirectoryPath(""); - setGenPath(""); + setExternalStorage(true); } - }, [isOpen, mode, workflowName, saveDirectoryPath, generationsPath]); - - const handleBrowse = async (target: "workflow" | "generations") => { - const setIsBrowsing = target === "workflow" ? setIsBrowsingWorkflow : setIsBrowsingGen; - const setPath = target === "workflow" ? setDirectoryPath : setGenPath; + }, [isOpen, mode, workflowName, saveDirectoryPath, useExternalImageStorage]); + const handleBrowse = async () => { setIsBrowsing(true); setError(null); @@ -60,7 +56,7 @@ export function ProjectSetupModal({ } if (result.path) { - setPath(result.path); + setDirectoryPath(result.path); } } catch (err) { setError( @@ -78,7 +74,7 @@ export function ProjectSetupModal({ } if (!directoryPath.trim()) { - setError("Workflow directory is required"); + setError("Project directory is required"); return; } @@ -86,57 +82,39 @@ export function ProjectSetupModal({ setError(null); try { - // Validate workflow directory exists + // Validate project directory exists const response = await fetch( `/api/workflow?path=${encodeURIComponent(directoryPath.trim())}` ); const result = await response.json(); if (!result.exists) { - setError("Workflow directory does not exist"); + setError("Project directory does not exist"); setIsValidating(false); return; } if (!result.isDirectory) { - setError("Workflow path is not a directory"); + setError("Project path is not a directory"); setIsValidating(false); return; } - // Validate generations directory if provided - if (genPath.trim()) { - const genResponse = await fetch( - `/api/workflow?path=${encodeURIComponent(genPath.trim())}` - ); - const genResult = await genResponse.json(); - - if (!genResult.exists) { - setError("Generations directory does not exist"); - setIsValidating(false); - return; - } - - if (!genResult.isDirectory) { - setError("Generations path is not a directory"); - setIsValidating(false); - return; - } - } - const id = mode === "new" ? generateWorkflowId() : useWorkflowStore.getState().workflowId || generateWorkflowId(); - onSave(id, name.trim(), directoryPath.trim(), genPath.trim() || null); + // Update external storage setting + setUseExternalImageStorage(externalStorage); + onSave(id, name.trim(), directoryPath.trim()); setIsValidating(false); } catch (err) { setError( - `Failed to validate directories: ${err instanceof Error ? err.message : "Unknown error"}` + `Failed to validate directory: ${err instanceof Error ? err.message : "Unknown error"}` ); setIsValidating(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !isValidating && !isBrowsingWorkflow && !isBrowsingGen) { + if (e.key === "Enter" && !isValidating && !isBrowsing) { handleSave(); } if (e.key === "Escape") { @@ -146,8 +124,6 @@ export function ProjectSetupModal({ if (!isOpen) return null; - const isBrowsing = isBrowsingWorkflow || isBrowsingGen; - return (
setDirectoryPath(e.target.value)} - placeholder="/Users/username/projects" + placeholder="/Users/username/projects/my-project" className="flex-1 px-3 py-2 bg-neutral-900 border border-neutral-600 rounded text-neutral-100 text-sm focus:outline-none focus:border-neutral-500" />

- Where the workflow JSON file will be saved + Workflow files and images will be saved here. Subfolders for inputs and generations will be auto-created.

-
- -
+
+
-

- Generated images will be automatically saved here -

+
+ Embed images as base64 +

+ Embeds all images in workflow, larger workflow files. Can hit memory limits on very large workflows. +

+
+
{error &&

{error}

} diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index e34a5f1a..45414a22 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -992,11 +992,11 @@ export function WorkflowCanvas() { if (jsonFiles.length > 0) { const file = jsonFiles[0]; const reader = new FileReader(); - reader.onload = (e) => { + reader.onload = async (e) => { try { const workflow = JSON.parse(e.target?.result as string) as WorkflowFile; if (workflow.version && workflow.nodes && workflow.edges) { - loadWorkflow(workflow); + await loadWorkflow(workflow); } else { alert("Invalid workflow file format"); } @@ -1084,8 +1084,8 @@ export function WorkflowCanvas() { {/* Welcome Modal */} {isCanvasEmpty && showQuickstart && ( { - loadWorkflow(workflow); + onWorkflowGenerated={async (workflow) => { + await loadWorkflow(workflow); setShowQuickstart(false); }} onClose={() => setShowQuickstart(false)} diff --git a/src/store/workflowStore.ts b/src/store/workflowStore.ts index 60eec863..2af3cb56 100644 --- a/src/store/workflowStore.ts +++ b/src/store/workflowStore.ts @@ -29,6 +29,7 @@ import { import { useToast } from "@/components/Toast"; import { calculateGenerationCost } from "@/utils/costCalculator"; import { logger } from "@/utils/logger"; +import { externalizeWorkflowImages, hydrateWorkflowImages } from "@/utils/imageStorage"; export type EdgeStyle = "angular" | "curved"; @@ -105,7 +106,7 @@ interface WorkflowStore { // Save/Load saveWorkflow: (name?: string) => void; - loadWorkflow: (workflow: WorkflowFile) => void; + loadWorkflow: (workflow: WorkflowFile, workflowPath?: string) => Promise; clearWorkflow: () => void; // Helpers @@ -127,12 +128,14 @@ interface WorkflowStore { hasUnsavedChanges: boolean; autoSaveEnabled: boolean; isSaving: boolean; + useExternalImageStorage: boolean; // Store images as separate files vs embedded base64 // Auto-save actions - setWorkflowMetadata: (id: string, name: string, path: string, generationsPath: string | null) => void; + setWorkflowMetadata: (id: string, name: string, path: string, generationsPath?: string | null) => void; setWorkflowName: (name: string) => void; setGenerationsPath: (path: string | null) => void; setAutoSaveEnabled: (enabled: boolean) => void; + setUseExternalImageStorage: (enabled: boolean) => void; markAsUnsaved: () => void; saveToFile: () => Promise; initializeAutoSave: () => void; @@ -343,6 +346,7 @@ export const useWorkflowStore = create((set, get) => ({ hasUnsavedChanges: false, autoSaveEnabled: true, isSaving: false, + useExternalImageStorage: true, // Default: store images as separate files // Cost tracking initial state incurredCost: 0, @@ -1765,7 +1769,7 @@ export const useWorkflowStore = create((set, get) => ({ URL.revokeObjectURL(url); }, - loadWorkflow: (workflow: WorkflowFile) => { + loadWorkflow: async (workflow: WorkflowFile, workflowPath?: string) => { // Update nodeIdCounter to avoid ID collisions const maxNodeId = workflow.nodes.reduce((max, node) => { const match = node.id.match(/-(\d+)$/); @@ -1790,20 +1794,34 @@ export const useWorkflowStore = create((set, get) => ({ const configs = loadSaveConfigs(); const savedConfig = workflow.id ? configs[workflow.id] : null; + // Determine the workflow directory path (passed in or from saved config) + const directoryPath = workflowPath || savedConfig?.directoryPath; + + // Hydrate images if we have a directory path and the workflow has image refs + let hydratedWorkflow = workflow; + if (directoryPath) { + try { + hydratedWorkflow = await hydrateWorkflowImages(workflow, directoryPath); + } catch (error) { + console.error("Failed to hydrate workflow images:", error); + // Continue with original workflow if hydration fails + } + } + // Load cost data for this workflow const costData = workflow.id ? loadWorkflowCostData(workflow.id) : null; set({ - nodes: workflow.nodes, - edges: workflow.edges, - edgeStyle: workflow.edgeStyle || "angular", - groups: workflow.groups || {}, + nodes: hydratedWorkflow.nodes, + edges: hydratedWorkflow.edges, + edgeStyle: hydratedWorkflow.edgeStyle || "angular", + groups: hydratedWorkflow.groups || {}, isRunning: false, currentNodeId: null, // Restore workflow ID and paths from localStorage if available workflowId: workflow.id || null, workflowName: workflow.name, - saveDirectoryPath: savedConfig?.directoryPath || null, + saveDirectoryPath: directoryPath || null, generationsPath: savedConfig?.generationsPath || null, lastSavedAt: savedConfig?.lastSavedAt || null, hasUnsavedChanges: false, @@ -1847,12 +1865,16 @@ export const useWorkflowStore = create((set, get) => ({ }, // Auto-save actions - setWorkflowMetadata: (id: string, name: string, path: string, generationsPath: string | null) => { + setWorkflowMetadata: (id: string, name: string, path: string, generationsPath?: string | null) => { + // Auto-derive generationsPath: use provided value, fall back to existing, then auto-derive + const currentGenPath = get().generationsPath; + const derivedGenerationsPath = generationsPath ?? currentGenPath ?? `${path}/generations`; + set({ workflowId: id, workflowName: name, saveDirectoryPath: path, - generationsPath: generationsPath, + generationsPath: derivedGenerationsPath, }); }, @@ -1873,6 +1895,10 @@ export const useWorkflowStore = create((set, get) => ({ set({ autoSaveEnabled: enabled }); }, + setUseExternalImageStorage: (enabled: boolean) => { + set({ useExternalImageStorage: enabled }); + }, + markAsUnsaved: () => { set({ hasUnsavedChanges: true }); }, @@ -1886,6 +1912,7 @@ export const useWorkflowStore = create((set, get) => ({ workflowId, workflowName, saveDirectoryPath, + useExternalImageStorage, } = get(); if (!workflowId || !workflowName || !saveDirectoryPath) { @@ -1895,7 +1922,7 @@ export const useWorkflowStore = create((set, get) => ({ set({ isSaving: true }); try { - const workflow: WorkflowFile = { + let workflow: WorkflowFile = { version: 1, id: workflowId, name: workflowName, @@ -1905,6 +1932,11 @@ export const useWorkflowStore = create((set, get) => ({ groups: Object.keys(groups).length > 0 ? groups : undefined, }; + // If external image storage is enabled, externalize images before saving + if (useExternalImageStorage) { + workflow = await externalizeWorkflowImages(workflow, saveDirectoryPath); + } + const response = await fetch("/api/workflow", { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/src/types/index.ts b/src/types/index.ts index e94eef6e..605a3520 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -43,6 +43,7 @@ export interface BaseNodeData extends Record { // Image Input Node Data export interface ImageInputNodeData extends BaseNodeData { image: string | null; + imageRef?: string; // External image reference for storage optimization filename: string | null; dimensions: { width: number; height: number } | null; } @@ -101,8 +102,10 @@ export type AnnotationShape = // Annotation Node Data export interface AnnotationNodeData extends BaseNodeData { sourceImage: string | null; + sourceImageRef?: string; // External image reference for storage optimization annotations: AnnotationShape[]; outputImage: string | null; + outputImageRef?: string; // External image reference for storage optimization } // Prompt Node Data @@ -132,8 +135,10 @@ export interface CarouselImageItem { // Nano Banana Node Data (Image Generation) export interface NanoBananaNodeData extends BaseNodeData { inputImages: string[]; // Now supports multiple images + inputImageRefs?: string[]; // External image references for storage optimization inputPrompt: string | null; outputImage: string | null; + outputImageRef?: string; // External image reference for storage optimization aspectRatio: AspectRatio; resolution: Resolution; // Only used by Nano Banana Pro model: ModelType; @@ -148,6 +153,7 @@ export interface NanoBananaNodeData extends BaseNodeData { export interface LLMGenerateNodeData extends BaseNodeData { inputPrompt: string | null; inputImages: string[]; + inputImageRefs?: string[]; // External image references for storage optimization outputText: string | null; provider: LLMProvider; model: LLMModelType; @@ -160,11 +166,13 @@ export interface LLMGenerateNodeData extends BaseNodeData { // Output Node Data export interface OutputNodeData extends BaseNodeData { image: string | null; + imageRef?: string; // External image reference for storage optimization } // Split Grid Node Data (Utility Node) export interface SplitGridNodeData extends BaseNodeData { sourceImage: string | null; + sourceImageRef?: string; // External image reference for storage optimization targetCount: number; // 4, 6, 8, 9, or 10 defaultPrompt: string; generateSettings: { diff --git a/src/utils/imageStorage.ts b/src/utils/imageStorage.ts new file mode 100644 index 00000000..32797dd0 --- /dev/null +++ b/src/utils/imageStorage.ts @@ -0,0 +1,427 @@ +import { WorkflowNode, WorkflowNodeData } from "@/types"; +import { WorkflowFile } from "@/store/workflowStore"; + +/** + * Generate a unique image ID for external storage + */ +export function generateImageId(): string { + const timestamp = Date.now().toString(36); + const random = Math.random().toString(36).substring(2, 8); + return `img-${timestamp}-${random}`; +} + +/** + * Check if a string is a base64 data URL + */ +function isBase64DataUrl(str: string | null | undefined): str is string { + return typeof str === "string" && str.startsWith("data:"); +} + +/** + * Extract and save all images from a workflow, replacing base64 data with refs + * Returns a new workflow object with image refs instead of base64 data + */ +export async function externalizeWorkflowImages( + workflow: WorkflowFile, + workflowPath: string +): Promise { + const externalizedNodes: WorkflowNode[] = []; + const savedImageIds = new Map(); // base64 hash -> imageId (for deduplication) + + for (const node of workflow.nodes) { + const newNode = await externalizeNodeImages(node, workflowPath, savedImageIds); + externalizedNodes.push(newNode); + } + + return { + ...workflow, + nodes: externalizedNodes, + }; +} + +/** + * Externalize images from a single node + */ +async function externalizeNodeImages( + node: WorkflowNode, + workflowPath: string, + savedImageIds: Map +): Promise { + const data = node.data as WorkflowNodeData; + let newData: WorkflowNodeData; + + switch (node.type) { + case "imageInput": { + const d = data as import("@/types").ImageInputNodeData; + if (isBase64DataUrl(d.image)) { + const imageId = await saveImageAndGetId(d.image, workflowPath, savedImageIds, "inputs"); + newData = { + ...d, + image: null, + imageRef: imageId, + }; + } else { + newData = d; + } + break; + } + + case "annotation": { + const d = data as import("@/types").AnnotationNodeData; + let sourceImageRef = d.sourceImageRef; + let outputImageRef = d.outputImageRef; + let sourceImage = d.sourceImage; + let outputImage = d.outputImage; + + // Annotation images are user-created, save to inputs + if (isBase64DataUrl(d.sourceImage)) { + sourceImageRef = await saveImageAndGetId(d.sourceImage, workflowPath, savedImageIds, "inputs"); + sourceImage = null; + } + if (isBase64DataUrl(d.outputImage)) { + outputImageRef = await saveImageAndGetId(d.outputImage, workflowPath, savedImageIds, "inputs"); + outputImage = null; + } + + newData = { + ...d, + sourceImage, + sourceImageRef, + outputImage, + outputImageRef, + }; + break; + } + + case "nanoBanana": { + const d = data as import("@/types").NanoBananaNodeData; + let outputImageRef = d.outputImageRef; + let outputImage = d.outputImage; + const inputImageRefs: string[] = []; + const inputImages: string[] = []; + + // Handle output image - AI generated, save to generations + if (isBase64DataUrl(d.outputImage)) { + outputImageRef = await saveImageAndGetId(d.outputImage, workflowPath, savedImageIds, "generations"); + outputImage = null; + } + + // Handle input images array (these come from connected nodes, save to inputs if present) + for (const img of d.inputImages) { + if (isBase64DataUrl(img)) { + const ref = await saveImageAndGetId(img, workflowPath, savedImageIds, "inputs"); + inputImageRefs.push(ref); + inputImages.push(""); // Empty placeholder + } else { + inputImages.push(img); + } + } + + newData = { + ...d, + inputImages: inputImages.length > 0 && inputImages.every(i => i === "") ? [] : inputImages, + inputImageRefs: inputImageRefs.length > 0 ? inputImageRefs : undefined, + outputImage, + outputImageRef, + }; + break; + } + + case "llmGenerate": { + const d = data as import("@/types").LLMGenerateNodeData; + const inputImageRefs: string[] = []; + const inputImages: string[] = []; + + // Handle input images array (save to inputs) + for (const img of d.inputImages) { + if (isBase64DataUrl(img)) { + const ref = await saveImageAndGetId(img, workflowPath, savedImageIds, "inputs"); + inputImageRefs.push(ref); + inputImages.push(""); // Empty placeholder + } else { + inputImages.push(img); + } + } + + newData = { + ...d, + inputImages: inputImages.length > 0 && inputImages.every(i => i === "") ? [] : inputImages, + inputImageRefs: inputImageRefs.length > 0 ? inputImageRefs : undefined, + }; + break; + } + + case "output": { + const d = data as import("@/types").OutputNodeData; + // Output displays generated content, save to generations + if (isBase64DataUrl(d.image)) { + const imageId = await saveImageAndGetId(d.image, workflowPath, savedImageIds, "generations"); + newData = { + ...d, + image: null, + imageRef: imageId, + }; + } else { + newData = d; + } + break; + } + + case "splitGrid": { + const d = data as import("@/types").SplitGridNodeData; + // SplitGrid source is input content, save to inputs + if (isBase64DataUrl(d.sourceImage)) { + const imageId = await saveImageAndGetId(d.sourceImage, workflowPath, savedImageIds, "inputs"); + newData = { + ...d, + sourceImage: null, + sourceImageRef: imageId, + }; + } else { + newData = d; + } + break; + } + + default: + newData = data; + } + + return { + ...node, + data: newData, + } as WorkflowNode; +} + +/** + * Save an image and return its ID (with deduplication) + * @param folder - "inputs" for user-uploaded images, "generations" for AI-generated images + */ +async function saveImageAndGetId( + imageData: string, + workflowPath: string, + savedImageIds: Map, + folder: "inputs" | "generations" = "inputs" +): Promise { + // Create a hash using length + samples from different parts of the data + // This avoids issues where all images of the same format have identical headers + // Include folder in hash so same image in different folders gets different IDs + const len = imageData.length; + const mid = Math.floor(len / 2); + const hash = `${folder}-${len}-${imageData.substring(50, 100)}-${imageData.substring(mid, mid + 50)}-${imageData.substring(Math.max(0, len - 50))}`; + + if (savedImageIds.has(hash)) { + return savedImageIds.get(hash)!; + } + + const imageId = generateImageId(); + + const response = await fetch("/api/workflow-images", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + workflowPath, + imageId, + imageData, + folder, + }), + }); + + const result = await response.json(); + + if (!result.success) { + throw new Error(`Failed to save image: ${result.error}`); + } + + savedImageIds.set(hash, imageId); + return imageId; +} + +/** + * Load all external images into a workflow, replacing refs with base64 data + * Returns a new workflow object with base64 data instead of refs + */ +export async function hydrateWorkflowImages( + workflow: WorkflowFile, + workflowPath: string +): Promise { + const hydratedNodes: WorkflowNode[] = []; + const loadedImages = new Map(); // imageId -> base64 (for caching) + + for (const node of workflow.nodes) { + const newNode = await hydrateNodeImages(node, workflowPath, loadedImages); + hydratedNodes.push(newNode); + } + + return { + ...workflow, + nodes: hydratedNodes, + }; +} + +/** + * Hydrate images for a single node + */ +async function hydrateNodeImages( + node: WorkflowNode, + workflowPath: string, + loadedImages: Map +): Promise { + const data = node.data as WorkflowNodeData; + let newData: WorkflowNodeData; + + switch (node.type) { + case "imageInput": { + const d = data as import("@/types").ImageInputNodeData; + if (d.imageRef && !d.image) { + const image = await loadImageById(d.imageRef, workflowPath, loadedImages, "inputs"); + newData = { + ...d, + image, + }; + } else { + newData = d; + } + break; + } + + case "annotation": { + const d = data as import("@/types").AnnotationNodeData; + let sourceImage = d.sourceImage; + let outputImage = d.outputImage; + + if (d.sourceImageRef && !d.sourceImage) { + sourceImage = await loadImageById(d.sourceImageRef, workflowPath, loadedImages, "inputs"); + } + if (d.outputImageRef && !d.outputImage) { + outputImage = await loadImageById(d.outputImageRef, workflowPath, loadedImages, "inputs"); + } + + newData = { + ...d, + sourceImage, + outputImage, + }; + break; + } + + case "nanoBanana": { + const d = data as import("@/types").NanoBananaNodeData; + let outputImage = d.outputImage; + const inputImages = [...d.inputImages]; + + if (d.outputImageRef && !d.outputImage) { + outputImage = await loadImageById(d.outputImageRef, workflowPath, loadedImages, "generations"); + } + + // Hydrate input images from refs + if (d.inputImageRefs && d.inputImageRefs.length > 0) { + for (let i = 0; i < d.inputImageRefs.length; i++) { + const ref = d.inputImageRefs[i]; + if (ref) { + inputImages[i] = await loadImageById(ref, workflowPath, loadedImages, "inputs"); + } + } + } + + newData = { + ...d, + inputImages, + outputImage, + }; + break; + } + + case "llmGenerate": { + const d = data as import("@/types").LLMGenerateNodeData; + const inputImages = [...d.inputImages]; + + // Hydrate input images from refs + if (d.inputImageRefs && d.inputImageRefs.length > 0) { + for (let i = 0; i < d.inputImageRefs.length; i++) { + const ref = d.inputImageRefs[i]; + if (ref) { + inputImages[i] = await loadImageById(ref, workflowPath, loadedImages, "inputs"); + } + } + } + + newData = { + ...d, + inputImages, + }; + break; + } + + case "output": { + const d = data as import("@/types").OutputNodeData; + if (d.imageRef && !d.image) { + const image = await loadImageById(d.imageRef, workflowPath, loadedImages, "generations"); + newData = { + ...d, + image, + }; + } else { + newData = d; + } + break; + } + + case "splitGrid": { + const d = data as import("@/types").SplitGridNodeData; + if (d.sourceImageRef && !d.sourceImage) { + const sourceImage = await loadImageById(d.sourceImageRef, workflowPath, loadedImages, "inputs"); + newData = { + ...d, + sourceImage, + }; + } else { + newData = d; + } + break; + } + + default: + newData = data; + } + + return { + ...node, + data: newData, + } as WorkflowNode; +} + +/** + * Load an image by ID (with caching) + * @param folder - Optional hint for which folder to check first + */ +async function loadImageById( + imageId: string, + workflowPath: string, + loadedImages: Map, + folder?: "inputs" | "generations" +): Promise { + if (loadedImages.has(imageId)) { + return loadedImages.get(imageId)!; + } + + const params = new URLSearchParams({ + workflowPath, + imageId, + }); + if (folder) { + params.set("folder", folder); + } + + const response = await fetch(`/api/workflow-images?${params.toString()}`); + + const result = await response.json(); + + if (!result.success) { + console.error(`Failed to load image ${imageId}: ${result.error}`); + return ""; // Return empty string on error to avoid breaking the workflow + } + + loadedImages.set(imageId, result.image); + return result.image; +}