diff --git a/package-lock.json b/package-lock.json index 59a9e162..64a697ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "@types/three": "^0.182.0", "@vitejs/plugin-react": "^4.7.0", "@vitest/coverage-v8": "^4.0.16", + "baseline-browser-mapping": "^2.10.0", "jsdom": "^27.4.0", "typescript": "^5.9.3", "vite-tsconfig-paths": "^6.0.4", @@ -1966,9 +1967,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -1980,9 +1981,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -1994,9 +1995,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", - "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -2008,9 +2009,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -2022,9 +2023,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", - "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -2036,9 +2037,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -2050,9 +2051,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -2064,9 +2065,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -2078,9 +2079,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -2092,9 +2093,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -2106,9 +2107,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "cpu": [ "loong64" ], @@ -2120,9 +2121,9 @@ ] }, "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -2134,9 +2135,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "cpu": [ "ppc64" ], @@ -2148,9 +2149,9 @@ ] }, "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -2162,9 +2163,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -2176,9 +2177,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -2190,9 +2191,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -2204,9 +2205,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -2218,9 +2219,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -2232,9 +2233,9 @@ ] }, "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "cpu": [ "x64" ], @@ -2246,9 +2247,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", - "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -2260,9 +2261,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -2274,9 +2275,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -2288,9 +2289,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -2302,9 +2303,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "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==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -3425,12 +3426,15 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.32", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", - "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/bidi-js": { @@ -4812,12 +4816,12 @@ } }, "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { - "jwa": "^2.0.0", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -5818,12 +5822,12 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6461,9 +6465,9 @@ } }, "node_modules/rollup": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", - "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -6477,31 +6481,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@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", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, diff --git a/package.json b/package.json index 2b9898b1..331a3dde 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@types/three": "^0.182.0", "@vitejs/plugin-react": "^4.7.0", "@vitest/coverage-v8": "^4.0.16", + "baseline-browser-mapping": "^2.10.0", "jsdom": "^27.4.0", "typescript": "^5.9.3", "vite-tsconfig-paths": "^6.0.4", diff --git a/server.js b/server.js index 931531ca..b32d4b94 100644 --- a/server.js +++ b/server.js @@ -1,3 +1,4 @@ +// Modified with the help of Antigravity - Charlie // Custom Next.js server with extended timeout for video generation // Node.js default server.requestTimeout is 5 minutes (300,000ms) // We extend it to 10 minutes for long-running fal.ai video generation diff --git a/src/app/page.tsx b/src/app/page.tsx index 0d0dd081..3dfa3371 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,4 @@ +// Modified with the help of Antigravity - Charlie "use client"; import { useEffect } from "react"; diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index 2064f614..711d0516 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -628,7 +628,7 @@ export function WorkflowCanvas() { if (numHandles > 0) { // Find the first unoccupied indexed handle by checking existing edges and batchUsed for (let i = 0; i < numHandles; i++) { - const candidateHandle = `${handleType}-${i}`; + const candidateHandle = i === 0 ? handleType : `${handleType}-${i}`; const isOccupied = edges.some( (edge) => edge.target === node.id && edge.targetHandle === candidateHandle ) || batchUsed?.has(candidateHandle); diff --git a/src/components/nodes/BaseNode.tsx b/src/components/nodes/BaseNode.tsx index 61bfcdf8..edf5bf76 100644 --- a/src/components/nodes/BaseNode.tsx +++ b/src/components/nodes/BaseNode.tsx @@ -33,6 +33,7 @@ interface BaseNodeProps { headerButtons?: ReactNode; titlePrefix?: ReactNode; commentNavigation?: CommentNavigationProps; + handles?: ReactNode; } export function BaseNode({ @@ -56,6 +57,7 @@ export function BaseNode({ headerButtons, titlePrefix, commentNavigation, + handles, }: BaseNodeProps) { const currentNodeIds = useWorkflowStore((state) => state.currentNodeIds); const groups = useWorkflowStore((state) => state.groups); @@ -260,6 +262,14 @@ export function BaseNode({ ${className} `} > + {/* Handles Overlay - Absolute sibling to everything else for perfect positioning */} + {handles && ( +
+
+ {handles} +
+
+ )}
{/* Title Section */}
diff --git a/src/components/nodes/GenerateAudioNode.tsx b/src/components/nodes/GenerateAudioNode.tsx index 123cab37..cab17696 100644 --- a/src/components/nodes/GenerateAudioNode.tsx +++ b/src/components/nodes/GenerateAudioNode.tsx @@ -210,17 +210,35 @@ export function GenerateAudioNode({ id, data, selected }: NodeProps { if (!nodeData.inputSchema || nodeData.inputSchema.length === 0) return null; + let textCount = 0; + let imageCount = 0; + let audioCount = 0; + return nodeData.inputSchema.map((input, index) => { - const handleType = input.type === "image" ? "image" : "text"; + const handleType = input.type === "image" || input.type === "audio" ? input.type : "text"; + + let handleId = ""; + if (handleType === "image") { + handleId = imageCount === 0 ? "image" : `image-${imageCount}`; + imageCount++; + } else if (handleType === "audio") { + handleId = audioCount === 0 ? "audio" : `audio-${audioCount}`; + audioCount++; + } else { + handleId = textCount === 0 ? "text" : `text-${textCount}`; + textCount++; + } + return ( ; +/** + * GenerateImageNode component for AI image generation. + * Handles model selection, parameter configuration, and image preview/history. + * @param props - Node properties from React Flow. + * @returns React component for the image generation node. + */ export function GenerateImageNode({ id, data, selected }: NodeProps) { const nodeData = data; const commentNavigation = useCommentNavigation(id); @@ -94,6 +100,10 @@ export function GenerateImageNode({ id, data, selected }: NodeProps { if (currentProvider === "gemini") { setExternalModels([]); @@ -144,6 +154,11 @@ export function GenerateImageNode({ id, data, selected }: NodeProps) => { const provider = e.target.value as ProviderType; @@ -172,6 +187,10 @@ export function GenerateImageNode({ id, data, selected }: NodeProps) => { const modelId = e.target.value; @@ -208,6 +227,10 @@ export function GenerateImageNode({ id, data, selected }: NodeProps) => { const model = e.target.value as ModelType; @@ -285,10 +308,18 @@ export function GenerateImageNode({ id, data, selected }: NodeProps state.regenerateNode); const isRunning = useWorkflowStore((state) => state.isRunning); + /** + * Triggers the regeneration of the current node's image. + */ const handleRegenerate = useCallback(() => { regenerateNode(id); }, [id, regenerateNode]); + /** + * Loads an image by its unique ID from the local generations path. + * @param imageId - The ID of the image to load. + * @returns A promise resolving to the base64 image data or null if not found. + */ const loadImageById = useCallback(async (imageId: string) => { if (!generationsPath) { console.error("Generations path not configured"); @@ -359,6 +390,10 @@ export function GenerateImageNode({ id, data, selected }: NodeProps { const newSelectedModel: SelectedModel = { provider: model.provider, @@ -473,66 +508,17 @@ export function GenerateImageNode({ id, data, selected }: NodeProps + +
Image
+ +
Prompt
+ +
Image
+ + } > - {/* Input handles - ALWAYS use same IDs and positions for connection stability */} - {/* Image input at 35%, Text input at 65% - never changes regardless of model */} - - {/* Image label */} -
- Image -
- - {/* Prompt label */} -
- Prompt -
- {/* Output handle */} - - {/* Output label */} -
- Image -
{/* Preview area */} diff --git a/src/components/nodes/GenerateVideoNode.tsx b/src/components/nodes/GenerateVideoNode.tsx index f2770b3e..bbe2ea7a 100644 --- a/src/components/nodes/GenerateVideoNode.tsx +++ b/src/components/nodes/GenerateVideoNode.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useCallback, useState, useEffect, useMemo, useRef } from "react"; -import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react"; +import { Handle, Position, NodeProps, Node, useReactFlow, useUpdateNodeInternals } from "@xyflow/react"; import { BaseNode } from "./BaseNode"; import { useCommentNavigation } from "@/hooks/useCommentNavigation"; import { ModelParameters } from "./ModelParameters"; @@ -14,6 +14,7 @@ import { useToast } from "@/components/Toast"; import { getVideoDimensions, calculateNodeSizePreservingHeight } from "@/utils/nodeDimensions"; import { ProviderBadge } from "./ProviderBadge"; import { useVideoBlobUrl } from "@/hooks/useVideoBlobUrl"; +import { isVeoModel, getVeoMetadata } from "@/utils/veoUtils"; // Video generation capabilities const VIDEO_CAPABILITIES: ModelCapability[] = ["text-to-video", "image-to-video"]; @@ -23,32 +24,61 @@ const VEO_ASPECT_RATIOS = ["16:9", "9:16"] as const; const VEO_DURATIONS = ["4", "6", "8"] as const; const VEO_RESOLUTIONS = ["720p", "1080p", "4k"] as const; -/** Returns true for Gemini-native Veo video models */ -function isVeoModel(modelId: string | undefined): boolean { - if (!modelId) return false; - return modelId.startsWith("veo-"); -} - -/** Build the hardcoded inputSchema for a Veo model, or undefined for non-Veo */ +/** + * Builds a hardcoded input schema for Veo models. + * @param modelId - The model ID to build the schema for. + * @returns Array of input definitions or undefined if not a Veo model. + */ function buildVeoInputSchema(modelId: string): ModelInputDef[] | undefined { - if (!isVeoModel(modelId)) return undefined; - const isI2V = modelId.includes("image-to-video"); - const inputs: ModelInputDef[] = [ - { name: "prompt", type: "text", required: true, label: "Prompt" }, - { name: "negative_prompt", type: "text", required: false, label: "Neg. Prompt" }, - ]; - if (isI2V) { - inputs.unshift({ name: "image", type: "image", required: true, label: "Image" }); + const metadata = getVeoMetadata(modelId); + if (!metadata.isVeo) return undefined; + + const schema: ModelInputDef[] = []; + + // Only include image for Image-to-Video models + if (metadata.isI2V) { + schema.push({ + name: metadata.imageParamName, + type: "image", + required: true, + label: "Image", + description: "Starting image frame for video" + }); } - return inputs; + + // Prompt and Neg Prompt are always included for Veo + schema.push({ + name: "prompt", + type: "text", + required: true, + label: "Prompt", + description: "Text description of the video to generate" + }); + + schema.push({ + name: "negative_prompt", + type: "text", + required: false, + label: "Neg. Prompt", + description: "Things to avoid in the generated video" + }); + + return schema; } type GenerateVideoNodeType = Node; +/** + * GenerateVideoNode component for AI video generation. + * Supports Google Veo and other external video models. + * @param props - Node properties from React Flow. + * @returns React component for the video generation node. + */ export function GenerateVideoNode({ id, data, selected }: NodeProps) { const nodeData = data; const commentNavigation = useCommentNavigation(id); const updateNodeData = useWorkflowStore((state) => state.updateNodeData); + const updateNodeInternals = useUpdateNodeInternals(); // Use stable selector for API keys to prevent unnecessary re-fetches const { geminiApiKey, replicateApiKey, falApiKey, kieApiKey, replicateEnabled, kieEnabled } = useProviderApiKeys(); const generationsPath = useWorkflowStore((state) => state.generationsPath); @@ -82,6 +112,10 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps { setIsLoadingModels(true); setModelsFetchError(null); @@ -179,6 +213,11 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps { const current = nodeData.parameters || {}; @@ -218,18 +257,49 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps updateNodeInternals(id)); }, - [id, setNodes] + [id, setNodes, updateNodeInternals] ); + + // Migrate existing/legacy Veo nodes to use the new stable schema automatically + useEffect(() => { + const modelId = nodeData.selectedModel?.modelId; + if (isVeoModel(modelId)) { + const stableSchema = buildVeoInputSchema(modelId!); + // Use JSON comparison to avoid infinite update loops + const currentSchemaJson = JSON.stringify(nodeData.inputSchema); + const stableSchemaJson = JSON.stringify(stableSchema); + + if (currentSchemaJson !== stableSchemaJson) { + updateNodeData(id, { inputSchema: stableSchema }); + } + } + }, [id, nodeData.selectedModel?.modelId, nodeData.inputSchema, updateNodeData]); + + // Update React Flow internals when schema changes so handles are correctly positioned + useEffect(() => { + updateNodeInternals(id); + }, [id, nodeData.inputSchema, updateNodeInternals]); const regenerateNode = useWorkflowStore((state) => state.regenerateNode); const isRunning = useWorkflowStore((state) => state.isRunning); + /** + * Triggers the regeneration of the current node's video. + */ const handleRegenerate = useCallback(() => { regenerateNode(id); }, [id, regenerateNode]); // Load video by ID from generations folder + /** + * Loads a video by its unique ID from the local generations path. + * @param videoId - The ID of the video/generation to load. + * @returns A promise resolving to the video data or null if not found. + */ const loadVideoById = useCallback(async (videoId: string) => { if (!generationsPath) { console.error("Generations path not configured"); @@ -301,6 +371,10 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps { const newSelectedModel: SelectedModel = { provider: model.provider, @@ -383,9 +457,12 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps updateNodeInternals(id)); }); }); - }, [id, nodeData.outputVideo, setNodes]); + }, [id, nodeData.outputVideo, setNodes, updateNodeInternals]); return ( <> @@ -403,213 +480,113 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps - {/* Dynamic input handles based on model schema */} - {nodeData.inputSchema && nodeData.inputSchema.length > 0 ? ( - // Render handles from schema, sorted by type (images first, text second) - // IMPORTANT: Always render "image" and "text" handles to maintain connection - // compatibility. Schema may only have text inputs (text-to-video models) but - // we still need the image handle to preserve connections made before model selection. - (() => { - const imageInputs = nodeData.inputSchema!.filter(i => i.type === "image"); - const textInputs = nodeData.inputSchema!.filter(i => i.type === "text"); - - // Always include at least one image and one text handle for connection stability - const hasImageInput = imageInputs.length > 0; - const hasTextInput = textInputs.length > 0; - - // Build the handles array: schema inputs + fallback defaults if missing - const handles: Array<{ - id: string; - type: "image" | "text"; - label: string; - schemaName: string | null; - description: string | null; - isPlaceholder: boolean; - }> = []; - - // Add image handles from schema, or a placeholder if none exist - if (hasImageInput) { - imageInputs.forEach((input, index) => { - handles.push({ - // Always use indexed IDs for schema inputs for consistency - id: `image-${index}`, - type: "image", - label: input.label, - schemaName: input.name, - description: input.description || null, - isPlaceholder: false, - }); - }); - } else { - // No image inputs in schema - add placeholder to preserve connections - handles.push({ - id: "image", - type: "image", - label: "Image", - schemaName: null, - description: "Not used by this model", - isPlaceholder: true, - }); - } - - // Add text handles from schema, or a placeholder if none exist - if (hasTextInput) { - textInputs.forEach((input, index) => { - handles.push({ - // Always use indexed IDs for schema inputs for consistency - id: `text-${index}`, - type: "text", - label: input.label, - schemaName: input.name, - description: input.description || null, - isPlaceholder: false, + handles={ + <> + {/* Dynamic input handles based on model schema */} + {nodeData.inputSchema && nodeData.inputSchema.length > 0 ? ( + (() => { + const imageInputs = nodeData.inputSchema!.filter(i => i.type === "image"); + const textInputs = nodeData.inputSchema!.filter(i => i.type === "text"); + const hasImageInput = imageInputs.length > 0; + const hasTextInput = textInputs.length > 0; + + const handles: Array<{ + id: string; + type: "image" | "text"; + label: string; + schemaName: string | null; + description: string | null; + isPlaceholder: boolean; + }> = []; + + if (hasImageInput) { + imageInputs.forEach((input, index) => { + handles.push({ + id: index === 0 ? "image" : `image-${index}`, + type: "image", + label: input.label, + schemaName: input.name, + description: input.description || null, + isPlaceholder: false, + }); + }); + } + + if (hasTextInput) { + textInputs.forEach((input, index) => { + handles.push({ + id: index === 0 ? "text" : `text-${index}`, + type: "text", + label: input.label, + schemaName: input.name, + description: input.description || null, + isPlaceholder: false, + }); + }); + } + + const imageHandles = handles.filter(h => h.type === "image"); + const textHandles = handles.filter(h => h.type === "text"); + const totalSlots = imageHandles.length + textHandles.length + 1; + + return handles.map((handle) => { + const isImage = handle.type === "image"; + const typeIndex = isImage + ? imageHandles.findIndex(h => h.id === handle.id) + : textHandles.findIndex(h => h.id === handle.id); + const adjustedIndex = isImage ? typeIndex : imageHandles.length + 1 + typeIndex; + const topPercent = ((adjustedIndex + 1) / (totalSlots + 1)) * 100; + + // Hide handles that are placeholders (not supported by current model) + const isHidden = handle.isPlaceholder; + + return ( + + + {!isHidden && ( +
+ {handle.label} +
+ )} +
+ ); }); - }); - } else { - // No text inputs in schema - add placeholder to preserve connections - handles.push({ - id: "text", - type: "text", - label: "Prompt", - schemaName: null, - description: "Not used by this model", - isPlaceholder: true, - }); - } - - // Calculate positions - const imageHandles = handles.filter(h => h.type === "image"); - const textHandles = handles.filter(h => h.type === "text"); - const totalSlots = imageHandles.length + textHandles.length + 1; // +1 for gap - - const renderedHandles = handles.map((handle, index) => { - // Position: images first, then gap, then text - const isImage = handle.type === "image"; - const typeIndex = isImage - ? imageHandles.findIndex(h => h.id === handle.id) - : textHandles.findIndex(h => h.id === handle.id); - const adjustedIndex = isImage ? typeIndex : imageHandles.length + 1 + typeIndex; - const topPercent = ((adjustedIndex + 1) / (totalSlots + 1)) * 100; - - return ( - - - {/* Handle label - positioned outside node, above the connector */} -
- {handle.label} -
-
- ); - }); - - // Add hidden backward-compatibility handles for edges using non-indexed IDs - // This ensures edges created with "image"/"text" still work when schema uses "image-0"/"text-0" - // Note: No data-handletype to avoid being counted in tests - these are purely for edge routing - return ( + })() + ) : ( <> - {renderedHandles} - {hasImageInput && ( - - )} - {hasTextInput && ( - - )} + +
Image
+ +
Prompt
- ); - })() - ) : ( - // Default handles when no schema - <> - - {/* Default image label */} -
- Image -
- - {/* Default text label */} -
- Prompt -
+ )} + + {/* Video output handle */} + +
Video
- )} - {/* Video output */} - - {/* Output label */} -
- Video -
+ } + >
{/* Preview area */} diff --git a/src/store/utils/connectedInputs.ts b/src/store/utils/connectedInputs.ts index 2a852087..23878beb 100644 --- a/src/store/utils/connectedInputs.ts +++ b/src/store/utils/connectedInputs.ts @@ -45,6 +45,11 @@ export interface ConnectedInputs { /** * Helper to determine if a handle ID is an image type */ +/** + * Checks if a handle ID corresponds to an image input/output. + * @param handleId - The ID of the handle to check. + * @returns True if the handle is related to image data. + */ function isImageHandle(handleId: string | null | undefined): boolean { if (!handleId) return false; return handleId === "image" || handleId.startsWith("image-") || handleId.includes("frame"); @@ -53,6 +58,11 @@ function isImageHandle(handleId: string | null | undefined): boolean { /** * Helper to determine if a handle ID is a text type */ +/** + * Checks if a handle ID corresponds to a text input/output. + * @param handleId - The ID of the handle to check. + * @returns True if the handle is related to text data. + */ function isTextHandle(handleId: string | null | undefined): boolean { if (!handleId) return false; return handleId === "text" || handleId.startsWith("text-") || handleId.includes("prompt"); @@ -61,6 +71,13 @@ function isTextHandle(handleId: string | null | undefined): boolean { /** * Extract output data and type from a source node */ +/** + * Extracts the output data and its type from a source node based on the handle. + * @param sourceNode - The node from which data originates. + * @param sourceHandle - The specific handle ID on the source node. + * @param edgeData - Optional metadata associated with the workflow edge. + * @returns An object containing the data type and the actual value. + */ function getSourceOutput( sourceNode: WorkflowNode, sourceHandle: string | null | undefined, @@ -123,6 +140,16 @@ function getSourceOutput( * Get all connected inputs for a node. * Pure function version of workflowStore.getConnectedInputs. */ +/** + * Recursively retrieves all connected input data for a specific node. + * This is a pure function used for dependency tracking and data flow. + * @param nodeId - The ID of the target node. + * @param nodes - The complete list of nodes in the workflow. + * @param edges - The complete list of edges in the workflow. + * @param visited - A set of already visited node IDs to prevent infinite loops. + * @param dimmedNodeIds - Optional set of node IDs that are considered inactive. + * @returns A structured object containing all connected inputs (images, text, etc.). + */ export function getConnectedInputsPure( nodeId: string, nodes: WorkflowNode[], @@ -150,6 +177,7 @@ export function getConnectedInputsPure( if (inputSchema && inputSchema.length > 0) { const imageInputs = inputSchema.filter(i => i.type === "image"); const textInputs = inputSchema.filter(i => i.type === "text"); + const audioInputs = inputSchema.filter(i => i.type === "audio"); imageInputs.forEach((input, index) => { handleToSchemaName[`image-${index}`] = input.name; @@ -164,6 +192,13 @@ export function getConnectedInputsPure( handleToSchemaName["text"] = input.name; } }); + + audioInputs.forEach((input, index) => { + handleToSchemaName[`audio-${index}`] = input.name; + if (index === 0) { + handleToSchemaName["audio"] = input.name; + } + }); } edges @@ -285,12 +320,27 @@ export function getConnectedInputsPure( model3d = value; } else if (type === "video") { videos.push(value); - } else if (type === "audio") { + } else if (type === "audio" || handleId === "audio" || handleId?.startsWith("audio-")) { audio.push(value); } else if (type === "text" || isTextHandle(handleId)) { - // Defensive: ensure text values are always strings - // (Guards against corrupted node data during parallel execution) - text = typeof value === 'string' ? value : String(value); + // Multi-handle text input handling: + // By default, the first text input (prompt) should populate the main 'text' variable. + // Secondary inputs (e.g. text-1/negative_prompt) are handled via dynamicInputs. + const stringValue = typeof value === "string" ? value : String(value); + + // Normalize handleId: if it's the primary handle for prompt, set the root 'text' + let isPrimaryText = !handleId || handleId === "text" || handleId === "text-0" || handleId === "prompt"; + if (inputSchema && inputSchema.length > 0) { + const textInputs = inputSchema.filter((i) => i.type === "text"); + if (textInputs.length > 0 && handleId) { + const schemaName = handleToSchemaName[handleId]; + isPrimaryText = schemaName === textInputs[0].name; + } + } + + if (isPrimaryText) { + text = stringValue; + } } else if (isImageHandle(handleId) || !handleId) { images.push(value); } @@ -320,6 +370,12 @@ export function getConnectedInputsPure( * Validate workflow structure. * Pure function version of workflowStore.validateWorkflow. */ +/** + * Validates the integrity and requirements of the workflow structure. + * @param nodes - The list of nodes to validate. + * @param edges - The list of edges to validate. + * @returns An object indicating if the workflow is valid and a list of error messages. + */ export function validateWorkflowPure( nodes: WorkflowNode[], edges: WorkflowEdge[] diff --git a/src/types/nodes.ts b/src/types/nodes.ts index 65725dbd..2769c7a1 100644 --- a/src/types/nodes.ts +++ b/src/types/nodes.ts @@ -154,7 +154,7 @@ export interface CarouselVideoItem { */ export interface ModelInputDef { name: string; - type: "image" | "text"; + type: "image" | "text" | "audio"; required: boolean; label: string; description?: string; diff --git a/src/utils/veoUtils.ts b/src/utils/veoUtils.ts new file mode 100644 index 00000000..746b76d0 --- /dev/null +++ b/src/utils/veoUtils.ts @@ -0,0 +1,32 @@ +/** + * Utility functions for Google Veo models. + */ + +/** + * Checks if a model ID corresponds to a Google Veo model. + * Supports native Gemini IDs (e.g., "veo-") and Kie IDs (e.g., "veo3/"). + * @param modelId - The model ID to check. + * @returns True if the model is a Veo model. + */ +export function isVeoModel(modelId: string | undefined): boolean { + if (!modelId) return false; + const id = modelId.toLowerCase(); + return id.startsWith("veo-") || id.startsWith("veo3/") || id.includes("/veo"); +} + +/** + * Returns complete metadata for a Veo model. + * @param modelId - The ID of the model. + * @returns An object containing Veo status, I2V status, and correct image parameter name. + */ +export function getVeoMetadata(modelId: string | undefined): { isVeo: boolean; isI2V: boolean; imageParamName: string } { + if (!isVeoModel(modelId)) { + return { isVeo: false, isI2V: false, imageParamName: "image" }; + } + + const id = modelId!.toLowerCase(); + const isI2V = id.includes("image-to-video") || id.includes("i2v") || id.includes("image to video"); + const imageParamName = id.startsWith("veo3") ? "imageUrls" : "image"; + + return { isVeo: true, isI2V, imageParamName }; +}