diff --git a/package-lock.json b/package-lock.json index 93f28458..72143230 100644 --- a/package-lock.json +++ b/package-lock.json @@ -531,6 +531,21 @@ "@floating-ui/utils": "^0.2.8" } }, + "node_modules/@floating-ui/react": { + "version": "0.26.24", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.24.tgz", + "integrity": "sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/react-dom": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", @@ -548,6 +563,34 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" }, + "node_modules/@headlessui/react": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.1.8.tgz", + "integrity": "sha512-uajqVkAcVG/wHwG9Fh5PFMcFpf2VxM4vNRNKxRjuK009kePVur8LkuuygHfIE+2uZ7z7GnlTtYsyUe6glPpTLg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@tanstack/react-virtual": "^3.8.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18", + "react-dom": "^18" + } + }, + "node_modules/@huggingface/jinja": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.2.2.tgz", + "integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -941,6 +984,70 @@ "node": ">=14" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@radix-ui/colors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", @@ -2174,6 +2281,89 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "node_modules/@react-aria/focus": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.18.2.tgz", + "integrity": "sha512-Jc/IY+StjA3uqN73o6txKQ527RFU7gnG5crEl5Xy3V+gbYp2O5L3ezAo/E0Ipi2cyMbG6T5Iit1IDs7hcGu8aw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.22.2", + "@react-aria/utils": "^3.25.2", + "@react-types/shared": "^3.24.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.22.2.tgz", + "integrity": "sha512-xE/77fRVSlqHp2sfkrMeNLrqf2amF/RyuAS6T5oDJemRSgYM3UoxTbWjucPhfnoW7r32pFPHHgz4lbdX8xqD/g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.5", + "@react-aria/utils": "^3.25.2", + "@react-types/shared": "^3.24.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.5.tgz", + "integrity": "sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.25.2.tgz", + "integrity": "sha512-GdIvG8GBJJZygB4L2QJP1Gabyn2mjFsha73I2wSe+o4DYeGWoJiMZRM06PyTIxLH4S7Sn7eVDtsSBfkc2VY/NA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.5", + "@react-stately/utils": "^3.10.3", + "@react-types/shared": "^3.24.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.3.tgz", + "integrity": "sha512-moClv7MlVSHpbYtQIkm0Cx+on8Pgt1XqtPx6fy9rQFb2DNc9u1G3AUVnqA17buOkH1vLxAtX4MedlxMWyRCYYA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-types/shared": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.24.1.tgz", + "integrity": "sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.21.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", @@ -2654,6 +2844,15 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, + "node_modules/@swc/helpers": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@swc/types": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz", @@ -2699,6 +2898,33 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz", + "integrity": "sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.10.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.8.tgz", + "integrity": "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -2811,6 +3037,12 @@ "@types/node": "*" } }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.5.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", @@ -3489,6 +3721,20 @@ "vite": "^4 || ^5" } }, + "node_modules/@xenova/transformers": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz", + "integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==", + "license": "Apache-2.0", + "dependencies": { + "@huggingface/jinja": "^0.2.2", + "onnxruntime-web": "1.14.0", + "sharp": "^0.32.0" + }, + "optionalDependencies": { + "onnxruntime-node": "1.14.0" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -3738,12 +3984,65 @@ "node": ">= 0.4" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.0.tgz", + "integrity": "sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "b4a": "^1.6.6", + "streamx": "^2.20.0" + } + }, "node_modules/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -3752,6 +4051,26 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", @@ -3874,6 +4193,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/blurhash": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", @@ -3937,6 +4267,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-crc32": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", @@ -4133,6 +4487,12 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -4213,6 +4573,19 @@ "resolved": "plugins/cms-export", "link": true }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4233,6 +4606,16 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colorjs.io": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", @@ -4576,7 +4959,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -4591,7 +4973,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -4599,6 +4980,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4768,7 +5158,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -5129,6 +5518,15 @@ "node": ">=4" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", @@ -5173,6 +5571,12 @@ "node": ">=6.0.0" } }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -5348,6 +5752,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flatbuffers": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz", + "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==", + "license": "SEE LICENSE IN LICENSE.txt" + }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", @@ -5464,6 +5874,12 @@ "react-dom": "^18.2.0" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5533,6 +5949,12 @@ "node": ">=4" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5667,6 +6089,12 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC" + }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -5746,7 +6174,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -5820,6 +6247,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -6396,6 +6829,12 @@ "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6438,6 +6877,10 @@ "yallist": "^2.1.2" } }, + "node_modules/magic-alt": { + "resolved": "plugins/magic-alt", + "link": true + }, "node_modules/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", @@ -6628,6 +7071,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -6670,6 +7119,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "license": "MIT" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6691,6 +7146,18 @@ "node-gyp-build": "^4.2.2" } }, + "node_modules/node-abi": { + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.68.0.tgz", + "integrity": "sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", @@ -6862,6 +7329,50 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/onnx-proto": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/onnx-proto/-/onnx-proto-4.0.4.tgz", + "integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==", + "license": "MIT", + "dependencies": { + "protobufjs": "^6.8.8" + } + }, + "node_modules/onnxruntime-common": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz", + "integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==", + "license": "MIT" + }, + "node_modules/onnxruntime-node": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz", + "integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==", + "license": "MIT", + "optional": true, + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "onnxruntime-common": "~1.14.0" + } + }, + "node_modules/onnxruntime-web": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz", + "integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==", + "license": "MIT", + "dependencies": { + "flatbuffers": "^1.12.0", + "guid-typescript": "^1.0.9", + "long": "^4.0.0", + "onnx-proto": "^4.0.4", + "onnxruntime-common": "~1.14.0", + "platform": "^1.3.6" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7110,6 +7621,12 @@ "nice-napi": "^1.0.2" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, "node_modules/points-on-curve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", @@ -7282,13 +7799,76 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" } }, "node_modules/prettier": { @@ -7355,6 +7935,32 @@ "react-is": "^16.13.1" } }, + "node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -7370,7 +7976,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -7405,6 +8010,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "license": "MIT" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -7417,6 +8028,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -8094,7 +8729,6 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -8129,6 +8763,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/sharp/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8155,6 +8827,66 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/sitemap-urls": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/sitemap-urls/-/sitemap-urls-3.0.0.tgz", @@ -8264,6 +8996,20 @@ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==" }, + "node_modules/streamx": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", + "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -8759,6 +9505,12 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "3.4.12", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz", @@ -8796,6 +9548,40 @@ "node": ">=14.0.0" } }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.0.tgz", + "integrity": "sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8930,6 +9716,18 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/turbo": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.1.2.tgz", @@ -10901,6 +11699,229 @@ "react-dom": "^18.2.0" } }, + "plugins/magic-alt": { + "version": "0.0.0", + "dependencies": { + "@headlessui/react": "^2.1.8", + "@xenova/transformers": "^2.17.2", + "classnames": "^2.5.1", + "framer-motion": "^11.5.4", + "framer-plugin": "^0.3.1", + "react": "^18", + "react-dom": "^18", + "react-intersection-observer": "^9.13.1", + "vite-plugin-mkcert": "^1" + }, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^7", + "@typescript-eslint/parser": "^7", + "@vitejs/plugin-react-swc": "^3", + "autoprefixer": "^10.4.19", + "eslint": "^8", + "eslint-plugin-react-hooks": "^4", + "eslint-plugin-react-refresh": "^0", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "typescript": "^5.3.3", + "vite": "^5", + "vite-plugin-framer": "^0" + } + }, + "plugins/magic-alt/node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/magic-alt/node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/magic-alt/node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "plugins/magic-alt/node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/magic-alt/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "plugins/magic-alt/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/magic-alt/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "plugins/magic-alt/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "plugins/notion": { "version": "0.0.0", "dependencies": { diff --git a/plugins/magic-alt/.eslintrc.cjs b/plugins/magic-alt/.eslintrc.cjs new file mode 100644 index 00000000..9358989c --- /dev/null +++ b/plugins/magic-alt/.eslintrc.cjs @@ -0,0 +1,11 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"], + ignorePatterns: ["dist", ".eslintrc.cjs"], + parser: "@typescript-eslint/parser", + plugins: ["react-refresh"], + rules: { + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + }, +} diff --git a/plugins/magic-alt/.gitignore b/plugins/magic-alt/.gitignore new file mode 100644 index 00000000..94469931 --- /dev/null +++ b/plugins/magic-alt/.gitignore @@ -0,0 +1,24 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn + +# misc +.DS_Store +*.pem + +# files +my-plugin +dev-plugin +dist + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local diff --git a/plugins/magic-alt/README.md b/plugins/magic-alt/README.md new file mode 100644 index 00000000..9daba0c0 --- /dev/null +++ b/plugins/magic-alt/README.md @@ -0,0 +1,21 @@ +# Framer Plugin Template + +This is a template for using the Framer Plugin API in a TypeScript project. + +## Quickstart + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +To see the results, open Framer and ensure the "Plugin Beta" preference is enabled. Then open the plugins menu from the toolbar, and select "Open Development Plugin" + +Learn more: https://developers.framer.wiki/ diff --git a/plugins/magic-alt/framer.json b/plugins/magic-alt/framer.json new file mode 100644 index 00000000..9a48ab00 --- /dev/null +++ b/plugins/magic-alt/framer.json @@ -0,0 +1,6 @@ +{ + "id": "a414e2", + "name": "Magic Alt", + "modes": ["canvas", "editImage"], + "icon": "/icon.svg" +} diff --git a/plugins/magic-alt/index.html b/plugins/magic-alt/index.html new file mode 100644 index 00000000..17896d4f --- /dev/null +++ b/plugins/magic-alt/index.html @@ -0,0 +1,14 @@ + + + + + + + Magic Alt + + +
+ + + + diff --git a/plugins/magic-alt/package.json b/plugins/magic-alt/package.json new file mode 100644 index 00000000..539877f5 --- /dev/null +++ b/plugins/magic-alt/package.json @@ -0,0 +1,39 @@ +{ + "name": "magic-alt", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build --base=${PREFIX_BASE_PATH:+/$npm_package_name}/", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@headlessui/react": "^2.1.8", + "@xenova/transformers": "^2.17.2", + "classnames": "^2.5.1", + "framer-motion": "^11.5.4", + "framer-plugin": "^0.3.1", + "react": "^18", + "react-dom": "^18", + "react-intersection-observer": "^9.13.1", + "vite-plugin-mkcert": "^1" + }, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^7", + "@typescript-eslint/parser": "^7", + "@vitejs/plugin-react-swc": "^3", + "autoprefixer": "^10.4.19", + "eslint": "^8", + "eslint-plugin-react-hooks": "^4", + "eslint-plugin-react-refresh": "^0", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "typescript": "^5.3.3", + "vite": "^5", + "vite-plugin-framer": "^0" + } +} diff --git a/plugins/magic-alt/postcss.config.js b/plugins/magic-alt/postcss.config.js new file mode 100644 index 00000000..d41ad635 --- /dev/null +++ b/plugins/magic-alt/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/plugins/magic-alt/public/icon.svg b/plugins/magic-alt/public/icon.svg new file mode 100644 index 00000000..cf637da4 --- /dev/null +++ b/plugins/magic-alt/public/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/magic-alt/src/App.tsx b/plugins/magic-alt/src/App.tsx new file mode 100644 index 00000000..c722f6c8 --- /dev/null +++ b/plugins/magic-alt/src/App.tsx @@ -0,0 +1,319 @@ +import { framer } from "framer-plugin" +import { useState, useMemo, useCallback, useEffect } from "react" +import { useInView } from "react-intersection-observer" +import cx from "classnames" +import { useTranslation } from "./hooks/useTranslation" +import { FrameNodeWithImage, useNoAltImages } from "./hooks/useNoAltImages" +import { generateCaptions } from "./api" +import { removeItemAtIndex } from "./utils" +import { LANGUAGES } from "./constants" + +import { Spinner } from "./components/Spinner" +import { EllipsisIcon, MagicIcon } from "./components/Icons" +import { DropdownMenu } from "./components/DropdownMenu" +import { IconButton } from "./components/IconButton" +import { LanguageSelector } from "./components/LanguageSelector" + +interface ImageCaptionItem { + image: FrameNodeWithImage + caption: string + isLoading: boolean +} + +const CAPTION_GENERATION_BATCH_SIZE = 5 +const TRANSLATION_BATCH_SIZE = 2 + +const ENGLISH_LANGUAGE_INDEX = 24 +const DEFAULT_LANGUAGE = LANGUAGES[ENGLISH_LANGUAGE_INDEX] + +export function App() { + const { ref: scrollRef, inView: isAtBottom } = useInView({ threshold: 1 }) + const { loadModel, modelLoadProgress, isModelLoading, isModelReady, translate, isTranslating } = useTranslation() + + const [images] = useNoAltImages() + const [imageCaptionItems, setImageCaptionItems] = useState([]) + const isGeneratingCaptions = useMemo(() => imageCaptionItems.some(item => item.isLoading), [imageCaptionItems]) + const allCaptionsBlank = useMemo(() => imageCaptionItems.every(item => item.caption === ""), [imageCaptionItems]) + + const [newlyTranslatedIndices, setNewlyTranslatedIndices] = useState([]) + const [translatingIndices, setTranslatingIndices] = useState([]) + + const [targetLanguage, setTargetLanguage] = useState(DEFAULT_LANGUAGE) + const [sourceLanguage, setSourceLanguage] = useState(DEFAULT_LANGUAGE) + const requiresTranslation = useMemo( + () => sourceLanguage.value !== targetLanguage.value, + [sourceLanguage, targetLanguage] + ) + + useEffect(() => { + // Initialize image caption items when the images array changes + const items = images.map(image => ({ + image, + caption: "", + isLoading: false, + })) + + setImageCaptionItems(items) + }, [images]) + + useEffect(() => { + if (requiresTranslation && !isModelLoading && !isModelReady) { + loadModel() + framer.notify("Loading translation model", { variant: "info" }) + } + }, [requiresTranslation, isModelLoading, isModelReady, loadModel]) + + const translateCaptions = useCallback(async () => { + if (!requiresTranslation || !isModelReady || isTranslating) return + + for (let i = 0; i < imageCaptionItems.length; i += TRANSLATION_BATCH_SIZE) { + const batch = imageCaptionItems.slice(i, i + TRANSLATION_BATCH_SIZE).map(item => item.caption) + const batchIndices = Array.from({ length: TRANSLATION_BATCH_SIZE }, (_, index) => i + index).filter( + index => index < imageCaptionItems.length && imageCaptionItems[index].caption.trim() !== "" + ) + + if (batch.length === 0) continue + + // Update UI for captions being translated + setTranslatingIndices(prevIndices => [...prevIndices, ...batchIndices]) + + try { + const translatedBatch = await translate(batch, sourceLanguage.value, targetLanguage.value) + + // Update captions with their translated text + setImageCaptionItems(prevItems => { + return prevItems.map((item, index) => + batchIndices.includes(index) + ? { ...item, caption: translatedBatch[batchIndices.indexOf(index)] } + : item + ) + }) + + setNewlyTranslatedIndices(prev => [...prev, ...batchIndices]) + } catch (e) { + framer.notify(e instanceof Error ? e.message : String(e), { variant: "error" }) + } finally { + // Captions are now translated, remove them + setTranslatingIndices(prevIndices => prevIndices.filter(index => !batchIndices.includes(index))) + } + } + + setSourceLanguage(targetLanguage) + }, [imageCaptionItems, requiresTranslation, isModelReady, isTranslating, translate, sourceLanguage, targetLanguage]) + + useEffect(() => { + // Check for newly translated indices + if (newlyTranslatedIndices.length > 0) { + // Allow for animation to occur then remove + const timer = setTimeout(() => { + setNewlyTranslatedIndices([]) + }, 500) + + return () => clearTimeout(timer) + } + }, [newlyTranslatedIndices]) + + const handleTranslate = useCallback(async () => { + if (!requiresTranslation) { + framer.notify("Please select a different language", { variant: "warning" }) + return + } + + if (!isModelReady) { + framer.notify("Loading translation model", { variant: "info" }) + return + } + + if (allCaptionsBlank) return + + await translateCaptions() + }, [requiresTranslation, isModelReady, translateCaptions, allCaptionsBlank]) + + const handleCaptionsGeneration = useCallback(async () => { + const publishInfo = await framer.getPublishInfo() + const siteUrl = publishInfo.staging?.url + + if (!siteUrl) { + framer.notify("Please publish your site to staging", { variant: "error" }) + return + } + + setNewlyTranslatedIndices([]) + setSourceLanguage(DEFAULT_LANGUAGE) + setImageCaptionItems(prevItems => + prevItems.map(item => ({ + image: item.image, + isLoading: true, + caption: "", + })) + ) + + for (let i = 0; i < images.length; i += CAPTION_GENERATION_BATCH_SIZE) { + const endIndex = Math.min(i + CAPTION_GENERATION_BATCH_SIZE, images.length) + const imageBatch = images.slice(i, endIndex).map(img => img.backgroundImage.url) + + try { + const captionBatch = await generateCaptions(siteUrl, imageBatch) + setImageCaptionItems(prevItems => { + return prevItems.map((item, index) => { + // Update items in current batch with new captions + if (index >= i && index < i + captionBatch.data.length) { + return { + ...item, + caption: captionBatch.data[index - i].caption, + isLoading: false, + } + } + + return item + }) + }) + } catch (e) { + framer.notify(e instanceof Error ? e.message : String(e), { variant: "error" }) + } + } + }, [images]) + + const handleSaveCaptions = async () => { + await Promise.all( + imageCaptionItems.map(({ image }, i) => + image.setAttributes({ + backgroundImage: image.backgroundImage.cloneWithAttributes({ + altText: imageCaptionItems[i].caption, + }), + }) + ) + ) + + await framer.closePlugin("Alt text applied", { variant: "success" }) + } + + const handleUpdateCaption = (index: number, value: string) => { + setImageCaptionItems(prevItems => + prevItems.map((item, i) => (i === index ? { ...item, caption: value } : item)) + ) + } + + if (images.length === 0) { + return ( +
+

All images have Alt Text

+
+ ) + } + + return ( +
+
+ setTargetLanguage(lang)} + disabled={isTranslating || isGeneratingCaptions} + className="flex-1" + /> + + +

Translate

+
+
+
+
+

Image

+
+

Alt Text

+
+
+
+
+ {imageCaptionItems.map(({ image, isLoading, caption }, i) => ( +
+
framer.zoomIntoView([image.id], { maxZoom: 2 })} + > + Background thumbnail +
+
+
+ {isLoading ? ( +
+ +
+ ) : null} + handleUpdateCaption(i, e.target.value)} + disabled={isTranslating || isGeneratingCaptions} + /> +
+
+ setImageCaptionItems(prevItems => removeItemAtIndex(prevItems, i)), + }, + ]} + > + {({ isOpen }) => ( + + + + )} + +
+ ))} +
+
+ {!isAtBottom &&
} +
+
+
+
+
+ + +
+
+
+ ) +} diff --git a/plugins/magic-alt/src/api.ts b/plugins/magic-alt/src/api.ts new file mode 100644 index 00000000..7546f127 --- /dev/null +++ b/plugins/magic-alt/src/api.ts @@ -0,0 +1,34 @@ +interface Generation { + caption: string + image: string +} + +interface GeneratedCaptionResponse { + data: Generation[] + error: { + message: string + } | null +} + +const isLocal = () => window.location.hostname.includes("localhost") + +const API_URL = isLocal() ? "http://localhost:8787" : "https://magic-alt-plugin-api.sakibulislam25800.workers.dev" + +export async function generateCaptions(siteUrl: string, images: string[]): Promise { + const res = await fetch(`${API_URL}/generate-alt-text`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ siteUrl, images }), + }) + + const json = await res.json() + const error = json.error + + if (error) { + throw new Error(error.message) + } + + return json +} diff --git a/plugins/magic-alt/src/components/DropdownMenu.tsx b/plugins/magic-alt/src/components/DropdownMenu.tsx new file mode 100644 index 00000000..6cec3302 --- /dev/null +++ b/plugins/magic-alt/src/components/DropdownMenu.tsx @@ -0,0 +1,51 @@ +import React from "react" +import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react" + +interface MenuItemType { + label: string + onClick: () => void + icon?: React.ReactNode +} + +interface DropdownMenuProps { + menuItems: MenuItemType[] + children: (props: { isOpen: boolean }) => React.ReactNode +} + +export const DropdownMenu: React.FC = ({ children, menuItems }) => { + return ( + + {({ open }) => ( + <> + {children({ isOpen: open })} + + {menuItems.map((item, index) => ( + + {({ close }) => ( + + )} + + ))} + + + )} + + ) +} diff --git a/plugins/magic-alt/src/components/IconButton.tsx b/plugins/magic-alt/src/components/IconButton.tsx new file mode 100644 index 00000000..31d1bf1e --- /dev/null +++ b/plugins/magic-alt/src/components/IconButton.tsx @@ -0,0 +1,15 @@ +import React, { forwardRef } from "react" +import cx from "classnames" + +interface Props extends React.ButtonHTMLAttributes { + children: React.ReactNode + className?: string +} + +export const IconButton = forwardRef(({ className, children, ...rest }, ref) => { + return ( + + ) +}) diff --git a/plugins/magic-alt/src/components/Icons.tsx b/plugins/magic-alt/src/components/Icons.tsx new file mode 100644 index 00000000..a48df471 --- /dev/null +++ b/plugins/magic-alt/src/components/Icons.tsx @@ -0,0 +1,29 @@ +export const EllipsisIcon = () => ( + + + + + +) + +export const MagicIcon = () => ( + + + + +) diff --git a/plugins/magic-alt/src/components/LanguageSelector.tsx b/plugins/magic-alt/src/components/LanguageSelector.tsx new file mode 100644 index 00000000..8fdf35e6 --- /dev/null +++ b/plugins/magic-alt/src/components/LanguageSelector.tsx @@ -0,0 +1,117 @@ +import { useState, useEffect } from "react" +import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, ComboboxButton } from "@headlessui/react" +import cx from "classnames" +import { LANGUAGES } from "../constants" + +interface Language { + value: string + label: string + code: string +} + +interface Props { + language: Language + onChange: (language: Language) => void + className?: string + disabled?: boolean +} + +const LanguageCodeBadge = ({ language, className }: { language: Language; className?: string }) => ( +
+

{language.code.toUpperCase()}

+
+) + +export const LanguageSelector = ({ language, onChange, className, disabled }: Props) => { + const [query, setQuery] = useState(language.label) + const [filteredLanguages, setFilteredLanguages] = useState(LANGUAGES) + + useEffect(() => { + const filtered = + query === "" + ? LANGUAGES + : LANGUAGES.filter(lang => { + const lowercaseQuery = query.toLowerCase() + const lowercaseLabel = lang.label.toLowerCase() + + // Show all the languages when there's an exact match + if (lowercaseLabel === lowercaseQuery) { + return true + } + + return lowercaseLabel.includes(lowercaseQuery) + }) + + setFilteredLanguages(filtered) + }, [query]) + + return ( +
+ { + if (inputLanguage) { + onChange(inputLanguage) + } else { + // Keep the currently selected language if the + // input is blank + onChange(language) + } + }} + > +
+ + lang.label} + onChange={e => setQuery(e.target.value)} + onFocus={() => setFilteredLanguages(LANGUAGES)} + className="w-full !pl-[31px]" + /> + +
+ +
+
+ {filteredLanguages.length > 0 && ( + +
+
+ {filteredLanguages.map(lang => ( + + cx("cursor-pointer rounded-[5px] text-xs", { + "bg-white dark:bg-tertiary": !focus, + "bg-tint text-white": focus, + }) + } + > + {({ focus }) => ( +
+ + {lang.label} + + {lang.code} +
+ )} +
+ ))} +
+
+
+ )} +
+
+ ) +} diff --git a/plugins/magic-alt/src/components/Spinner.tsx b/plugins/magic-alt/src/components/Spinner.tsx new file mode 100644 index 00000000..594ad9d9 --- /dev/null +++ b/plugins/magic-alt/src/components/Spinner.tsx @@ -0,0 +1,40 @@ +import cx from "classnames" +import styles from "./spinner.module.css" + +export interface SpinnerProps { + /** Size of the spinner */ + size?: "normal" | "medium" | "large" + /** Set the spinner to have a static position inline with other content */ + inline?: boolean + className?: string + inheritColor?: boolean +} + +function styleForSize(size: SpinnerProps["size"]) { + switch (size) { + case "normal": + return styles.normalStyle + case "medium": + return styles.mediumStyle + case "large": + return styles.largeStyle + } +} + +function spinnerClassNames(size: SpinnerProps["size"] = "normal") { + return cx(styles.spin, styles.baseStyle, styleForSize(size)) +} + +export const Spinner = ({ size, inline = false, inheritColor, className, ...rest }: SpinnerProps) => { + return ( +
+ ) +} diff --git a/plugins/magic-alt/src/components/spinner.module.css b/plugins/magic-alt/src/components/spinner.module.css new file mode 100644 index 00000000..a0d1a7ac --- /dev/null +++ b/plugins/magic-alt/src/components/spinner.module.css @@ -0,0 +1,60 @@ +.baseStyle { + --spinner-translate: 0; + background-color: #fff; +} + +.buttonWithDepthSpinner { + background-color: currentColor; +} + +.normalStyle { + width: 12px; + height: 12px; + -webkit-mask: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAApNJREFUSA2tlUtLlFEYgEe7mtFFQ8NLFBG1SJAwahtiLVy5ceVSEPQH1LZf0Lp9FO1bdYNyI7gyBFcS2kAzilYq3sfxeYZ55Zv6FGfohWfOd97vnPd23u9MXeZoaeb1Q7gPHXAFzsAS5GACxiEPqVKXqs1kmtAPQi+cAtedToz1PJ8ExxPwDl7CAlRImoN7rBiF86ABHTiGAzMIh8n3O+ifwWc4EL0n5TGTYQijvjNSxbUadozInUeGZ3nuh1WYgpIkM+hGMwYajAgdrfUn+AaLoLTCAxiAOxCZxL4RdB+gZMjxEjwHo4hS8Jh5DR9hz0mKaNizegoNEI7Wee6FXJTISG6AUgDr+QImoAiHie+mwZJow/PRps4uwnsnRj8Epqc4voVJJ8eULOtW4BFYYjPpgjc+3IWI2sh/wFeoVl6xYQ48dDGLfjOwc/ygQqz595hUMVouS9QHBm4l6k3HWpnBLqicgVrlCxujSbR1Swd+UDpQYRS/oVaZZ2MWwlZBB9Zdxf+QTYw8SRgq6sAvz7vH6JULEB9USVHFjzZskgPRQR4ayxoXXIdaHdg9HrB2rErByWxZERnYtrWUzD0esDbtTmXPhzXwHrKLxEUbYGbVyDkWWwkdadfqbPmwDXZSCyh2VAf8BM/nOOJH5Y0QopMt2I5U/KO4DZZJB47XwEX+ex0mGroMV8sLwp56gyuGwiyW4SZ4c8bt2cZzO7jBNeqtsRHbeZ1g9krsMbhfYPv/c5gecA+4yAVuitFszE5Hjva872OMdXagF19JIoOYWyozMTI3J0tmA6gLvXPfO1cc/XOqOLe/HbjwD8yCXRD3VNJROEgatiRZMJsKsbZHia3nAdphtq8fkiWyXJbBe8uM1aXKPouNnz2Bm1kwAAAAAElFTkSuQmCC"); + mask: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAApNJREFUSA2tlUtLlFEYgEe7mtFFQ8NLFBG1SJAwahtiLVy5ceVSEPQH1LZf0Lp9FO1bdYNyI7gyBFcS2kAzilYq3sfxeYZ55Zv6FGfohWfOd97vnPd23u9MXeZoaeb1Q7gPHXAFzsAS5GACxiEPqVKXqs1kmtAPQi+cAtedToz1PJ8ExxPwDl7CAlRImoN7rBiF86ABHTiGAzMIh8n3O+ifwWc4EL0n5TGTYQijvjNSxbUadozInUeGZ3nuh1WYgpIkM+hGMwYajAgdrfUn+AaLoLTCAxiAOxCZxL4RdB+gZMjxEjwHo4hS8Jh5DR9hz0mKaNizegoNEI7Wee6FXJTISG6AUgDr+QImoAiHie+mwZJow/PRps4uwnsnRj8Epqc4voVJJ8eULOtW4BFYYjPpgjc+3IWI2sh/wFeoVl6xYQ48dDGLfjOwc/ygQqz595hUMVouS9QHBm4l6k3HWpnBLqicgVrlCxujSbR1Swd+UDpQYRS/oVaZZ2MWwlZBB9Zdxf+QTYw8SRgq6sAvz7vH6JULEB9USVHFjzZskgPRQR4ayxoXXIdaHdg9HrB2rErByWxZERnYtrWUzD0esDbtTmXPhzXwHrKLxEUbYGbVyDkWWwkdadfqbPmwDXZSCyh2VAf8BM/nOOJH5Y0QopMt2I5U/KO4DZZJB47XwEX+ex0mGroMV8sLwp56gyuGwiyW4SZ4c8bt2cZzO7jBNeqtsRHbeZ1g9krsMbhfYPv/c5gecA+4yAVuitFszE5Hjva872OMdXagF19JIoOYWyozMTI3J0tmA6gLvXPfO1cc/XOqOLe/HbjwD8yCXRD3VNJROEgatiRZMJsKsbZHia3nAdphtq8fkiWyXJbBe8uM1aXKPouNnz2Bm1kwAAAAAElFTkSuQmCC"); + -webkit-mask-size: 12px; + mask-size: 12px; +} + +.mediumStyle { + width: 24px; + height: 24px; + -webkit-mask: url("data:image/png;base64,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"); + mask: url("data:image/png;base64,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"); + -webkit-mask-size: 24px; + mask-size: 24px; +} + +.largeStyle { + width: 30px; + height: 30px; + -webkit-mask: url("data:image/png;base64,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"); + mask: url("data:image/png;base64,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"); + -webkit-mask-size: 30px; + mask-size: 30px; +} + +.centeredStyle { + --spinner-translate: -50%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(var(--spinner-translate), var(--spinner-translate)); +} + +.spin { + animation-duration: 800ms; + animation-iteration-count: infinite; + animation-name: spin; + animation-timing-function: linear; +} + +@keyframes spin { + 0% { + transform: translate(var(--spinner-translate), var(--spinner-translate)) rotate(0deg); + } + + 100% { + transform: translate(var(--spinner-translate), var(--spinner-translate)) rotate(360deg); + } +} diff --git a/plugins/magic-alt/src/constants.ts b/plugins/magic-alt/src/constants.ts new file mode 100644 index 00000000..a4cbed92 --- /dev/null +++ b/plugins/magic-alt/src/constants.ts @@ -0,0 +1,159 @@ +// List of languages in FLORES-200 is available here: +// https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200 + +export interface Language { + label: string + value: string + code: string +} + +export const LANGUAGES: Language[] = [ + { label: "Afrikaans", value: "afr_Latn", code: "af" }, + { label: "Akan", value: "aka_Latn", code: "ak" }, + { label: "Amharic", value: "amh_Ethi", code: "am" }, + { label: "Armenian", value: "hye_Armn", code: "hy" }, + { label: "Assamese", value: "asm_Beng", code: "as" }, + { label: "Bambara", value: "bam_Latn", code: "bm" }, + { label: "Bashkir", value: "bak_Cyrl", code: "ba" }, + { label: "Basque", value: "eus_Latn", code: "eu" }, + { label: "Belarusian", value: "bel_Cyrl", code: "be" }, + { label: "Bengali", value: "ben_Beng", code: "bn" }, + { label: "Bosnian", value: "bos_Latn", code: "bs" }, + { label: "Bulgarian", value: "bul_Cyrl", code: "bg" }, + { label: "Burmese", value: "mya_Mymr", code: "my" }, + { label: "Catalan", value: "cat_Latn", code: "ca" }, + { label: "Central Aymara", value: "ayr_Latn", code: "ay" }, + { label: "Chinese (Simplified)", value: "zho_Hans", code: "zh" }, + { label: "Chinese (Traditional)", value: "zho_Hant", code: "zh" }, + { label: "Croatian", value: "hrv_Latn", code: "hr" }, + { label: "Czech", value: "ces_Latn", code: "cs" }, + { label: "Danish", value: "dan_Latn", code: "da" }, + { label: "Dutch", value: "nld_Latn", code: "nl" }, + { label: "Dzongkha", value: "dzo_Tibt", code: "dz" }, + { label: "Eastern Panjabi", value: "pan_Guru", code: "pa" }, + { label: "Eastern Yiddish", value: "ydd_Hebr", code: "yi" }, + { label: "English", value: "eng_Latn", code: "en" }, + { label: "Esperanto", value: "epo_Latn", code: "eo" }, + { label: "Estonian", value: "est_Latn", code: "et" }, + { label: "Ewe", value: "ewe_Latn", code: "ee" }, + { label: "Faroese", value: "fao_Latn", code: "fo" }, + { label: "Fijian", value: "fij_Latn", code: "fj" }, + { label: "Finnish", value: "fin_Latn", code: "fi" }, + { label: "Fon", value: "fon_Latn", code: "fon" }, + { label: "French", value: "fra_Latn", code: "fr" }, + { label: "Galician", value: "glg_Latn", code: "gl" }, + { label: "Ganda", value: "lug_Latn", code: "lg" }, + { label: "Georgian", value: "kat_Geor", code: "ka" }, + { label: "German", value: "deu_Latn", code: "de" }, + { label: "Greek", value: "ell_Grek", code: "el" }, + { label: "Guarani", value: "grn_Latn", code: "gn" }, + { label: "Gujarati", value: "guj_Gujr", code: "gu" }, + { label: "Haitian Creole", value: "hat_Latn", code: "ht" }, + { label: "Halh Mongolian", value: "khk_Cyrl", code: "mn" }, + { label: "Hausa", value: "hau_Latn", code: "ha" }, + { label: "Hebrew", value: "heb_Hebr", code: "he" }, + { label: "Hindi", value: "hin_Deva", code: "hi" }, + { label: "Hungarian", value: "hun_Latn", code: "hu" }, + { label: "Icelandic", value: "isl_Latn", code: "is" }, + { label: "Igbo", value: "ibo_Latn", code: "ig" }, + { label: "Indonesian", value: "ind_Latn", code: "id" }, + { label: "Irish", value: "gle_Latn", code: "ga" }, + { label: "Italian", value: "ita_Latn", code: "it" }, + { label: "Japanese", value: "jpn_Jpan", code: "ja" }, + { label: "Javanese", value: "jav_Latn", code: "jv" }, + { label: "Kannada", value: "kan_Knda", code: "kn" }, + { label: "Kashmiri (Arabic script)", value: "kas_Arab", code: "ks" }, + { label: "Kashmiri (Devanagari script)", value: "kas_Deva", code: "ks" }, + { label: "Kazakh", value: "kaz_Cyrl", code: "kk" }, + { label: "Khmer", value: "khm_Khmr", code: "km" }, + { label: "Kikongo", value: "kon_Latn", code: "kg" }, + { label: "Kikuyu", value: "kik_Latn", code: "ki" }, + { label: "Kinyarwanda", value: "kin_Latn", code: "rw" }, + { label: "Korean", value: "kor_Hang", code: "ko" }, + { label: "Kyrgyz", value: "kir_Cyrl", code: "ky" }, + { label: "Lao", value: "lao_Laoo", code: "lo" }, + { label: "Latgalian", value: "ltg_Latn", code: "ltg" }, + { label: "Ligurian", value: "lij_Latn", code: "lij" }, + { label: "Limburgish", value: "lim_Latn", code: "li" }, + { label: "Lingala", value: "lin_Latn", code: "ln" }, + { label: "Lithuanian", value: "lit_Latn", code: "lt" }, + { label: "Luo", value: "luo_Latn", code: "luo" }, + { label: "Luxembourgish", value: "ltz_Latn", code: "lb" }, + { label: "Macedonian", value: "mkd_Cyrl", code: "mk" }, + { label: "Magahi", value: "mag_Deva", code: "mag" }, + { label: "Maithili", value: "mai_Deva", code: "mai" }, + { label: "Malayalam", value: "mal_Mlym", code: "ml" }, + { label: "Maltese", value: "mlt_Latn", code: "mt" }, + { label: "Maori", value: "mri_Latn", code: "mi" }, + { label: "Marathi", value: "mar_Deva", code: "mr" }, + { label: "Mizo", value: "lus_Latn", code: "lus" }, + { label: "Modern Standard Arabic (Romanized)", value: "arb_Latn", code: "ar" }, + { label: "Modern Standard Arabic", value: "arb_Arab", code: "ar" }, + { label: "Moroccan Arabic", value: "ary_Arab", code: "ary" }, + { label: "Nepali", value: "npi_Deva", code: "ne" }, + { label: "North Azerbaijani", value: "azj_Latn", code: "az" }, + { label: "Northern Sotho", value: "nso_Latn", code: "nso" }, + { label: "Northern Uzbek", value: "uzn_Latn", code: "uz" }, + { label: "Norwegian Bokmål", value: "nob_Latn", code: "nb" }, + { label: "Norwegian Nynorsk", value: "nno_Latn", code: "nn" }, + { label: "Nuer", value: "nus_Latn", code: "nus" }, + { label: "Nyanja", value: "nya_Latn", code: "ny" }, + { label: "Occitan", value: "oci_Latn", code: "oc" }, + { label: "Odia", value: "ory_Orya", code: "or" }, + { label: "Plateau Malagasy", value: "plt_Latn", code: "mg" }, + { label: "Polish", value: "pol_Latn", code: "pl" }, + { label: "Portuguese", value: "por_Latn", code: "pt" }, + { label: "Romanian", value: "ron_Latn", code: "ro" }, + { label: "Rundi", value: "run_Latn", code: "rn" }, + { label: "Russian", value: "rus_Cyrl", code: "ru" }, + { label: "Samoan", value: "smo_Latn", code: "sm" }, + { label: "Sango", value: "sag_Latn", code: "sg" }, + { label: "Sanskrit", value: "san_Deva", code: "sa" }, + { label: "Sardinian", value: "srd_Latn", code: "sc" }, + { label: "Scottish Gaelic", value: "gla_Latn", code: "gd" }, + { label: "Serbian", value: "srp_Cyrl", code: "sr" }, + { label: "Shan", value: "shn_Mymr", code: "shn" }, + { label: "Shona", value: "sna_Latn", code: "sn" }, + { label: "Sindhi", value: "snd_Arab", code: "sd" }, + { label: "Sinhala", value: "sin_Sinh", code: "si" }, + { label: "Slovak", value: "slk_Latn", code: "sk" }, + { label: "Slovenian", value: "slv_Latn", code: "sl" }, + { label: "Somali", value: "som_Latn", code: "so" }, + { label: "Southern Pashto", value: "pbt_Arab", code: "ps" }, + { label: "Southern Sotho", value: "sot_Latn", code: "st" }, + { label: "Spanish", value: "spa_Latn", code: "es" }, + { label: "Standard Latvian", value: "lvs_Latn", code: "lv" }, + { label: "Standard Malay", value: "zsm_Latn", code: "ms" }, + { label: "Standard Tibetan", value: "bod_Tibt", code: "bo" }, + { label: "Sundanese", value: "sun_Latn", code: "su" }, + { label: "Swahili", value: "swh_Latn", code: "sw" }, + { label: "Swati", value: "ssw_Latn", code: "ss" }, + { label: "Swedish", value: "swe_Latn", code: "sv" }, + { label: "Tagalog", value: "tgl_Latn", code: "tl" }, + { label: "Tajik", value: "tgk_Cyrl", code: "tg" }, + { label: "Tamil", value: "tam_Taml", code: "ta" }, + { label: "Tatar", value: "tat_Cyrl", code: "tt" }, + { label: "Telugu", value: "tel_Telu", code: "te" }, + { label: "Thai", value: "tha_Thai", code: "th" }, + { label: "Tigrinya", value: "tir_Ethi", code: "ti" }, + { label: "Tok Pisin", value: "tpi_Latn", code: "tpi" }, + { label: "Tosk Albanian", value: "als_Latn", code: "sq" }, + { label: "Tsonga", value: "tso_Latn", code: "ts" }, + { label: "Tswana", value: "tsn_Latn", code: "tn" }, + { label: "Tumbuka", value: "tum_Latn", code: "tum" }, + { label: "Turkish", value: "tur_Latn", code: "tr" }, + { label: "Turkmen", value: "tuk_Latn", code: "tk" }, + { label: "Twi", value: "twi_Latn", code: "tw" }, + { label: "Ukrainian", value: "ukr_Cyrl", code: "uk" }, + { label: "Urdu", value: "urd_Arab", code: "ur" }, + { label: "Uyghur", value: "uig_Arab", code: "ug" }, + { label: "Vietnamese", value: "vie_Latn", code: "vi" }, + { label: "Welsh", value: "cym_Latn", code: "cy" }, + { label: "West Central Oromo", value: "gaz_Latn", code: "om" }, + { label: "Western Persian", value: "pes_Arab", code: "fa" }, + { label: "Wolof", value: "wol_Latn", code: "wo" }, + { label: "Xhosa", value: "xho_Latn", code: "xh" }, + { label: "Yoruba", value: "yor_Latn", code: "yo" }, + { label: "Yue Chinese", value: "yue_Hant", code: "yue" }, + { label: "Zulu", value: "zul_Latn", code: "zu" }, +] diff --git a/plugins/magic-alt/src/globals.css b/plugins/magic-alt/src/globals.css new file mode 100644 index 00000000..cf0a7486 --- /dev/null +++ b/plugins/magic-alt/src/globals.css @@ -0,0 +1,81 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer utilities { + /* Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } +} + +@layer components { + .row { + display: flex; + flex-direction: row; + gap: 10px; + } + + .col { + display: flex; + flex-direction: column; + gap: 10px; + } + + .row-lg { + display: flex; + flex-direction: row; + gap: 15px; + } + + .col-lg { + display: flex; + flex-direction: column; + gap: 15px; + } + + .combo-bg { + @apply bg-white dark:bg-tertiary; + } + + .menu-shadow { + box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.1); + } + + .scroll-fade { + flex-shrink: 0; + height: 45px; + width: 100%; + display: block; + overflow: visible; + z-index: 10; + position: absolute; + left: 0; + bottom: 61px; + pointer-events: none; + } + + [data-framer-theme="light"] .scroll-fade { + background: linear-gradient(to bottom, transparent, white); + } + + [data-framer-theme="dark"] .scroll-fade { + background: linear-gradient(180deg, rgba(18, 18, 18, 0) 0%, rgb(17, 17, 17) 97.8%); + } + + .loading-transition { + transition: + width 150ms ease-in-out, + opacity 150ms ease-in-out 150ms; + } +} + +#root { + display: flex; + flex-direction: column; +} diff --git a/plugins/magic-alt/src/hooks/useNoAltImages.tsx b/plugins/magic-alt/src/hooks/useNoAltImages.tsx new file mode 100644 index 00000000..5283a0d4 --- /dev/null +++ b/plugins/magic-alt/src/hooks/useNoAltImages.tsx @@ -0,0 +1,32 @@ +import { FrameNode, framer } from "framer-plugin" +import { useEffect, useState } from "react" + +export type FrameNodeWithImage = FrameNode & { + backgroundImage: NonNullable +} + +export function useNoAltImages() { + const [images, setImages] = useState([]) + + useEffect(() => { + let active = true + + async function run() { + const imageNodes = await framer.getNodesWithAttributeSet("backgroundImage") + const imagesWithoutAlt = imageNodes.filter( + node => node.backgroundImage != null && !node.backgroundImage.altText + ) + + if (!active) return + setImages(imagesWithoutAlt as FrameNodeWithImage[]) + } + + run() + + return () => { + active = false + } + }, []) + + return [images, setImages] as const +} diff --git a/plugins/magic-alt/src/hooks/useTranslation.ts b/plugins/magic-alt/src/hooks/useTranslation.ts new file mode 100644 index 00000000..016bace1 --- /dev/null +++ b/plugins/magic-alt/src/hooks/useTranslation.ts @@ -0,0 +1,161 @@ +import { useEffect, useMemo, useRef, useState } from "react" +import { framer } from "framer-plugin" +import { Outputs, ProgressData, WorkerRequest } from "../translation.worker" + +interface InitPipelineEvent { + status: "initiate" + file: string +} + +interface InitPipelineProgressEvent extends ProgressData { + status: "progress" +} + +interface InitPipelineDoneEvent extends ProgressData { + status: "done" +} + +interface PipelineReadyEvent { + status: "ready" +} + +interface TranslateUpdateEvent { + status: "update" + outputs: Outputs +} + +interface TranslateCompleteEvent { + status: "complete" + outputs: Outputs +} + +interface ErrorEvent { + status: "error" + message: string +} + +type WorkerResponse = + | InitPipelineEvent + | InitPipelineProgressEvent + | InitPipelineDoneEvent + | PipelineReadyEvent + | TranslateUpdateEvent + | TranslateCompleteEvent + | ErrorEvent + +export const useTranslation = () => { + const worker = useRef(null) + + const [outputs, setOutputs] = useState([]) + const [isModelReady, setIsModelReady] = useState(false) + const [isModelLoading, setIsModelLoading] = useState(false) + const [isTranslating, setIsTranslating] = useState(false) + const [progressItems, setProgressItems] = useState([]) + + const translationResolve = useRef<((outputs: Outputs) => void) | null>(null) + const translationReject = useRef<((reason: Error) => void) | null>(null) + + const postMessage = (request: WorkerRequest) => { + return worker.current?.postMessage(request) + } + + const modelLoadProgress = useMemo(() => { + const hasDeterminedFileTotals = progressItems.every(item => item.total !== 0) + if (progressItems.length === 0 || !hasDeterminedFileTotals) return 0 + + const totalBytes = progressItems.reduce((sum, item) => sum + item.total, 0) + const loadedBytes = progressItems.reduce((sum, item) => sum + item.loaded, 0) + + if (totalBytes === 0) return 0 + + return Math.round((loadedBytes / totalBytes) * 100) + }, [progressItems]) + + useEffect(() => { + if (!worker.current) { + worker.current = new Worker(new URL("../translation.worker.ts", import.meta.url), { + type: "module", + }) + } + + const onMessageReceived = (e: MessageEvent) => { + const eventData = e.data + + switch (eventData.status) { + case "initiate": + // Model file start load: add to progress items for tracking + setProgressItems(prev => [...prev, { ...eventData, total: 0, loaded: 0, progress: 0 }]) + setIsModelLoading(true) + break + + case "progress": + // Model file progress: update progress items + setProgressItems(items => items.map(item => (item.file === eventData.file ? eventData : item))) + break + + case "update": + // Generation update: update the real-time outputs + setOutputs(eventData.outputs) + break + + case "ready": + // Pipeline ready: worker ready to accept messages + setIsModelLoading(false) + setIsModelReady(true) + break + + case "complete": + // Translation complete + setIsTranslating(false) + + if (translationResolve.current) { + translationResolve.current(eventData.outputs) + translationResolve.current = null + translationReject.current = null + } + + break + + case "error": + framer.notify(eventData.message, { variant: "error" }) + + if (translationReject.current) { + translationReject.current(new Error(eventData.message)) + translationResolve.current = null + translationReject.current = null + } + + break + } + } + + worker.current?.addEventListener("message", onMessageReceived) + + return () => worker.current?.removeEventListener("message", onMessageReceived) + }, []) + + const loadModel = () => { + postMessage({ action: "load_model" }) + } + + const translate = async (texts: string[], src_lang: string, tgt_lang: string): Promise => { + setIsTranslating(true) + + return new Promise((resolve: (outputs: Outputs) => void, reject) => { + translationResolve.current = resolve + translationReject.current = reject + + postMessage({ action: "translate", texts, src_lang, tgt_lang }) + }).finally(() => setIsTranslating(false)) + } + + return { + loadModel, + modelLoadProgress, + translate, + isTranslating, + isModelLoading, + isModelReady, + outputs, + } +} diff --git a/plugins/magic-alt/src/main.tsx b/plugins/magic-alt/src/main.tsx new file mode 100644 index 00000000..4f141d2f --- /dev/null +++ b/plugins/magic-alt/src/main.tsx @@ -0,0 +1,68 @@ +import "./globals.css" +import "framer-plugin/framer.css" + +import React from "react" +import ReactDOM from "react-dom/client" +import { App } from "./App" +import { framer } from "framer-plugin" +import { generateCaptions } from "./api" + +function renderPlugin() { + const root = document.getElementById("root") + if (!root) throw new Error("Root element not found") + + framer.showUI({ + position: "top right", + minWidth: 240, + minHeight: 300, + resizable: true, + }) + + ReactDOM.createRoot(root).render( + + + + ) +} + +async function runPlugin() { + const mode = framer.mode + + if (mode === "editImage") { + try { + const image = await framer.getImage() + + if (!image) { + await framer.closePlugin("No Image was selected.", { variant: "error" }) + return + } + + const siteInfo = await framer.getPublishInfo() + const siteUrl = siteInfo.staging?.url + + if (!siteUrl) { + await framer.closePlugin("Please publish your site to staging", { variant: "error" }) + return + } + + const captions = await generateCaptions(siteUrl, [image.url]) + + await framer.setImage({ + image: { + bytes: (await image.getData()).bytes, + mimeType: (await image.getData()).mimeType, + }, + altText: captions.data[0].caption, + }) + + await framer.closePlugin("Alt text generated", { variant: "success" }) + } catch (e) { + const message = e instanceof Error ? e.message : String(e) + framer.closePlugin("An unexpected error occurred: " + message, { variant: "error" }) + } + } + + renderPlugin() +} + +runPlugin() diff --git a/plugins/magic-alt/src/translation.worker.ts b/plugins/magic-alt/src/translation.worker.ts new file mode 100644 index 00000000..ad9bee05 --- /dev/null +++ b/plugins/magic-alt/src/translation.worker.ts @@ -0,0 +1,113 @@ +import { env, pipeline, TranslationOutput, TranslationPipeline } from "@xenova/transformers" +import { GenerationConfigType } from "@xenova/transformers/types/utils/generation" + +// See: https://github.com/xenova/transformers.js/issues/366 +env.allowLocalModels = false +env.allowRemoteModels = true +env.useBrowserCache = false + +// See: https://github.com/xenova/transformers.js/issues/270 +const originalWarn = console.warn + +console.warn = function (...args) { + if (!args[0].includes("onnxruntime")) { + originalWarn.apply(console, args) + } +} + +export interface TranslationRequest { + action: "translate" + texts: string[] + src_lang: string + tgt_lang: string +} + +export interface LoadModelRequest { + action: "load_model" +} + +export type WorkerRequest = TranslationRequest | LoadModelRequest + +export interface ProgressData { + file: string + progress: number + loaded: number + total: number +} + +export type Outputs = string[] + +interface ModelCallbackOutput { + output_token_ids: number[] +} + +class TranslationSingleton { + static instance: Promise | null = null + + static async getInstance(progress_callback: (data: ProgressData) => void) { + if (this.instance === null) { + this.instance = pipeline("translation", "Xenova/nllb-200-distilled-600M", { + progress_callback, + quantized: true, + }) + } + + return this.instance + } +} + +let translator: TranslationPipeline | null = null + +self.addEventListener("message", async (event: MessageEvent) => { + const eventData = event.data + + switch (eventData.action) { + case "load_model": { + translator = await TranslationSingleton.getInstance(x => { + // Track model loading progress + self.postMessage(x) + }) + + break + } + + case "translate": { + if (!translator) { + self.postMessage({ status: "error", message: "Please load the model before translating" }) + return + } + + const { texts, src_lang, tgt_lang } = eventData + + try { + const outputs = (await translator(texts, { + tgt_lang, + src_lang, + // Allow for partial output + callback_function: (x: ModelCallbackOutput[]) => { + const decodedOutputs = x.map(output => + translator!.tokenizer.decode(output.output_token_ids, { skip_special_tokens: true }) + ) + + self.postMessage({ + status: "update", + outputs: decodedOutputs, + }) + }, + } as GenerationConfigType)) as TranslationOutput + + self.postMessage({ + status: "complete", + outputs: outputs.map(output => output.translation_text), + }) + } catch (e) { + self.postMessage({ status: "error", message: e instanceof Error ? e.message : String(e) }) + } + + break + } + + default: + self.postMessage({ status: "error", message: "Unknown action" }) + } +}) diff --git a/plugins/magic-alt/src/utils.ts b/plugins/magic-alt/src/utils.ts new file mode 100644 index 00000000..3812e6ff --- /dev/null +++ b/plugins/magic-alt/src/utils.ts @@ -0,0 +1,4 @@ +export const removeItemAtIndex = (array: T[] | null, index: number): T[] => { + if (!array) return [] + return [...array.slice(0, index), ...array.slice(index + 1)] +} diff --git a/plugins/magic-alt/src/vite-env.d.ts b/plugins/magic-alt/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/plugins/magic-alt/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/plugins/magic-alt/tailwind.config.js b/plugins/magic-alt/tailwind.config.js new file mode 100644 index 00000000..d225cb35 --- /dev/null +++ b/plugins/magic-alt/tailwind.config.js @@ -0,0 +1,61 @@ +/** @type {import('tailwindcss').Config} */ +export default { + darkMode: ["class", '[data-framer-theme="dark"]'], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + theme: { + extend: { + backgroundColor: { + primary: "var(--framer-color-bg)", + secondary: "var(--framer-color-bg-secondary)", + tertiary: "var(--framer-color-bg-tertiary)", + tertiaryDimmedLight: "rgba(243, 243, 243, 0.75)", + tertiaryDimmedDark: "rgba(43, 43, 43, 0.75)", + divider: "var(--framer-color-divider)", + tint: "var(--framer-color-tint)", + tintDimmed: "var(--framer-color-tint-dimmed)", + tintDark: "var(--framer-color-tint-dark)", + blackDimmed: "rgba(0, 0, 0, 0.5)", + }, + colors: { + primary: "var(--framer-color-text)", + secondary: "var(--framer-color-text-secondary)", + tertiary: "var(--framer-color-text-tertiary)", + inverted: "var(--framer-color-text-inverted)", + "framer-red": "#FF3366", + "framer-yellow": "#FFC25A", + "framer-blue": "#0099FF", + "framer-green": "#00c25a", + }, + borderColor: { + divider: "var(--framer-color-divider)", + }, + fontSize: { + "2xs": "10px", + }, + spacing: { + 30: "30px", + 15: "15px", + }, + boxShadow: { + "bg-image": "inset 0 0 0 1px rgba(0, 0, 0, 0.1)", + }, + dropShadow: { + "locale-code": "0 1px 1px rgba(0, 0, 0, 0.2)", + }, + keyframes: { + pulse: { + "0%, 100%": { opacity: "1" }, + "50%": { opacity: "0.5" }, + }, + expand: { + from: { width: "0" }, + to: { width: "100%" }, + }, + }, + animation: { + pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite", + expand: "expand 0.5s ease-out forwards", + }, + }, + }, +} diff --git a/plugins/magic-alt/tsconfig.json b/plugins/magic-alt/tsconfig.json new file mode 100644 index 00000000..325606c6 --- /dev/null +++ b/plugins/magic-alt/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ES2022", + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/plugins/magic-alt/vite.config.ts b/plugins/magic-alt/vite.config.ts new file mode 100644 index 00000000..7efb6ef8 --- /dev/null +++ b/plugins/magic-alt/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vite" +import react from "@vitejs/plugin-react-swc" +import mkcert from "vite-plugin-mkcert" +import framer from "vite-plugin-framer" + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), mkcert(), framer()], + build: { + target: "ES2022", + }, +})