initial commit
221
client/package-lock.json
generated
|
@ -711,208 +711,266 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.0.tgz",
|
||||
"integrity": "sha512-/IZQvg6ZR0tAkEi4tdXOraQoWeJy9gbQ/cx4I7k9dJaCk9qrXEcdouxRVz5kZXt5C2bQ9pILoAA+KB4C/d3pfw==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
|
||||
"integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.0.tgz",
|
||||
"integrity": "sha512-ETHi4bxrYnvOtXeM7d4V4kZWixib2jddFacJjsOjwbgYSRsyXYtZHC4ht134OsslPIcnkqT+TKV4eU8rNBKyyQ==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
|
||||
"integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.0.tgz",
|
||||
"integrity": "sha512-ZWgARzhSKE+gVUX7QWaECoRQsPwaD8ZR0Oxb3aUpzdErTvlEadfQpORPXkKSdKbFci9v8MJfkTtoEHnnW9Ulng==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
|
||||
"integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.0.tgz",
|
||||
"integrity": "sha512-h0ZAtOfHyio8Az6cwIGS+nHUfRMWBDO5jXB8PQCARVF6Na/G6XS2SFxDl8Oem+S5ZsHQgtsI7RT4JQnI1qrlaw==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
|
||||
"integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
|
||||
"integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
|
||||
"integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.0.tgz",
|
||||
"integrity": "sha512-9pxQJSPwFsVi0ttOmqLY4JJ9pg9t1gKhK0JDbV1yUEETSx55fdyCjt39eBQ54OQCzAF0nVGO6LfEH1KnCPvelA==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
|
||||
"integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.0.tgz",
|
||||
"integrity": "sha512-YJ5Ku5BmNJZb58A4qSEo3JlIG4d3G2lWyBi13ABlXzO41SsdnUKi3HQHe83VpwBVG4jHFTW65jOQb8qyoR+qzg==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
|
||||
"integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.0.tgz",
|
||||
"integrity": "sha512-U4G4u7f+QCqHlVg1Nlx+qapZy+QoG+NV6ux+upo/T7arNGwKvKP2kmGM4W5QTbdewWFgudQxi3kDNST9GT1/mg==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.0.tgz",
|
||||
"integrity": "sha512-aQpNlKmx3amwkA3a5J6nlXSahE1ijl0L9KuIjVOUhfOh7uw2S4piR3mtpxpRtbnK809SBtyPsM9q15CPTsY7HQ==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
|
||||
"integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.0.tgz",
|
||||
"integrity": "sha512-9fx6Zj/7vve/Fp4iexUFRKb5+RjLCff6YTRQl4CoDhdMfDoobWmhAxQWV3NfShMzQk1Q/iCnageFyGfqnsmeqQ==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.0.tgz",
|
||||
"integrity": "sha512-VWQiCcN7zBgZYLjndIEh5tamtnKg5TGxyZPWcN9zBtXBwfcGSZ5cHSdQZfQH/GB4uRxk0D3VYbOEe/chJhPGLQ==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.0.tgz",
|
||||
"integrity": "sha512-EHmPnPWvyYqncObwqrosb/CpH3GOjE76vWVs0g4hWsDRUVhg61hBmlVg5TPXqF+g+PvIbqkC7i3h8wbn4Gp2Fg==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.0.tgz",
|
||||
"integrity": "sha512-tsSWy3YQzmpjDKnQ1Vcpy3p9Z+kMFbSIesCdMNgLizDWFhrLZIoN21JSq01g+MZMDFF+Y1+4zxgrlqPjid5ohg==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
|
||||
"integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.0.tgz",
|
||||
"integrity": "sha512-anr1Y11uPOQrpuU8XOikY5lH4Qu94oS6j0xrulHk3NkLDq19MlX8Ng/pVipjxBJ9a2l3+F39REZYyWQFkZ4/fw==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
|
||||
"integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.0.tgz",
|
||||
"integrity": "sha512-7LB+Bh+Ut7cfmO0m244/asvtIGQr5pG5Rvjz/l1Rnz1kDzM02pSX9jPaS0p+90H5I1x4d1FkCew+B7MOnoatNw==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
|
||||
"integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.0.tgz",
|
||||
"integrity": "sha512-+3qZ4rer7t/QsC5JwMpcvCVPRcJt1cJrYS/TMJZzXIJbxWFQEVhrIc26IhB+5Z9fT9umfVc+Es2mOZgl+7jdJQ==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
|
||||
"integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.0.tgz",
|
||||
"integrity": "sha512-YdicNOSJONVx/vuPkgPTyRoAPx3GbknBZRCOUkK84FJ/YTfs/F0vl/YsMscrB6Y177d+yDRcj+JWMPMCgshwrA==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
|
||||
"integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
|
@ -1138,10 +1196,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||
"dev": true
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.12.12",
|
||||
|
@ -1971,10 +2030,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
|
@ -3947,9 +4007,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -3957,6 +4017,7 @@
|
|||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
|
@ -4989,12 +5050,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.0.tgz",
|
||||
"integrity": "sha512-W21MUIFPZ4+O2Je/EU+GP3iz7PH4pVPUXSbEZdatQnxo29+3rsUjgrJmzuAZU24z7yRAnFN6ukxeAhZh/c7hzg==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
|
||||
"integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.5"
|
||||
"@types/estree": "1.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
|
@ -5004,22 +5066,25 @@
|
|||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.22.0",
|
||||
"@rollup/rollup-android-arm64": "4.22.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.22.0",
|
||||
"@rollup/rollup-darwin-x64": "4.22.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.22.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.22.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.22.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.22.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.22.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.22.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.22.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.22.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.22.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.22.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.22.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.22.0",
|
||||
"@rollup/rollup-android-arm-eabi": "4.28.1",
|
||||
"@rollup/rollup-android-arm64": "4.28.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.28.1",
|
||||
"@rollup/rollup-darwin-x64": "4.28.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.28.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.28.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.28.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.28.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.28.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.28.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.28.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.28.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.28.1",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
|
|
BIN
client/public/assets/images/demo/image1.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image10.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image11.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image12.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image13.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image14.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image15.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image16.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image17.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image18.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image19.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image2.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image20.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image3.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image4.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image5.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image6.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image7.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image8.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
client/public/assets/images/demo/image9.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
|
@ -1,6 +1,6 @@
|
|||
import { FC } from "react";
|
||||
|
||||
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
export const Button: FC<ButtonProps> = ({ children, className, ...props }) => {
|
||||
return (
|
||||
<button
|
||||
|
@ -11,3 +11,15 @@ export const Button: FC<ButtonProps> = ({ children, className, ...props }) => {
|
|||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const SwitchButton: FC<ButtonProps> = ({ children, className, ...props }) => {
|
||||
return (
|
||||
<button
|
||||
className={`border-0 disabled:text-white-100 border-white bg-black p-2 hover:text-purple-300 active:bg-gray-700 ${className ?? ""}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
140
client/src/components/ImageGallery/ImageGallery.tsx
Normal file
|
@ -0,0 +1,140 @@
|
|||
|
||||
import { useState } from "react";
|
||||
import { Button } from "../Button/Button";
|
||||
|
||||
// Natural images
|
||||
import img1 from "/assets/images/demo/image1.jpg";
|
||||
import img2 from "/assets/images/demo/image2.jpg";
|
||||
import img3 from "/assets/images/demo/image3.jpg";
|
||||
import img4 from "/assets/images/demo/image4.jpg";
|
||||
import img5 from "/assets/images/demo/image5.jpg";
|
||||
import img6 from "/assets/images/demo/image6.jpg";
|
||||
import img7 from "/assets/images/demo/image7.jpg";
|
||||
import img8 from "/assets/images/demo/image8.jpg";
|
||||
import img9 from "/assets/images/demo/image9.jpg";
|
||||
import img10 from "/assets/images/demo/image10.jpg";
|
||||
import img11 from "/assets/images/demo/image11.jpg";
|
||||
import img12 from "/assets/images/demo/image12.jpg";
|
||||
import img13 from "/assets/images/demo/image13.jpg";
|
||||
import img14 from "/assets/images/demo/image14.jpg";
|
||||
import img15 from "/assets/images/demo/image15.jpg";
|
||||
import img16 from "/assets/images/demo/image16.jpg";
|
||||
import img17 from "/assets/images/demo/image17.jpg";
|
||||
import img18 from "/assets/images/demo/image18.jpg";
|
||||
import img19 from "/assets/images/demo/image19.jpg";
|
||||
import img20 from "/assets/images/demo/image20.jpg";
|
||||
|
||||
const images = [
|
||||
img1,
|
||||
img2,
|
||||
img3,
|
||||
img4,
|
||||
img5,
|
||||
img6,
|
||||
img7,
|
||||
img8,
|
||||
img9,
|
||||
img10,
|
||||
img11,
|
||||
img12,
|
||||
img13,
|
||||
img14,
|
||||
img15,
|
||||
img16,
|
||||
img17,
|
||||
img18,
|
||||
img19,
|
||||
img20,
|
||||
]
|
||||
|
||||
var images_order: number[] = [];
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
images_order.push(i)
|
||||
}
|
||||
|
||||
type ImageGalleryProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||
// Properties for the ImageGallery
|
||||
paramsSetter: Function;
|
||||
clickAction: Function;
|
||||
size: number;
|
||||
numImages: number;
|
||||
}
|
||||
|
||||
|
||||
type ImageItemProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||
// Properties for a single item in the ImageGallery
|
||||
// Two actions:
|
||||
// paramsSetter sets the chosen image url into the model params
|
||||
// clickAction then starts the conversation
|
||||
paramsSetter: Function;
|
||||
clickAction: Function;
|
||||
size: number;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
|
||||
function ImageSelect(props: ImageItemProps) {
|
||||
// Represents a single image in the gallery
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsHover(true);
|
||||
};
|
||||
const handleMouseLeave = () => {
|
||||
setIsHover(false);
|
||||
};
|
||||
let bordercolor = isHover ? "#f7a319" : "black";
|
||||
let bgalpha = isHover ? 0.05 : 0.6;
|
||||
let textalpha = isHover ? 1.0 : 0.0
|
||||
let label = isHover ? "Connect" : "X";
|
||||
let style = {
|
||||
width: props.size,
|
||||
height: props.size,
|
||||
background: `url(${props.imageUrl})`,
|
||||
backgroundSize: "100% 100%",
|
||||
border: `3px solid ${bordercolor}`,
|
||||
margin: "2px",
|
||||
padding: "0px",
|
||||
color: `rgba(255, 255, 255, ${textalpha})`,
|
||||
boxShadow: `inset 0 0 0 1000px rgba(0,0,0,${bgalpha})`,
|
||||
textShadow: `2px 2px 2px rgba(0, 0, 0, ${textalpha})`
|
||||
};
|
||||
return (
|
||||
<button style={style} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}
|
||||
onClick={async () => { await props.paramsSetter(props.imageUrl); props.clickAction() }
|
||||
} > {label}</button >
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const shuffle = (array: number[]) => {
|
||||
return array.sort(() => Math.random() - 0.5);
|
||||
};
|
||||
|
||||
export const ImageGallery = (props: ImageGalleryProps) => {
|
||||
const [ordering, SetOrdering] = useState(images_order);
|
||||
|
||||
function handleShuffle() {
|
||||
SetOrdering(shuffle([...ordering]));
|
||||
}
|
||||
|
||||
// Image Gallery widget (random subset)
|
||||
const steps = [];
|
||||
for (let i = 0; i < props.numImages; i++) {
|
||||
steps.push(<ImageSelect
|
||||
key={"natural_" + ordering[i]}
|
||||
imageUrl={images[ordering[i]]} {...props}></ImageSelect >);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-center items-center p-2 flex-center" style={{ marginRight: "12%", marginLeft: "12%" }}>
|
||||
<span style={{ display: "flex", flex: 1 }}></span>
|
||||
<Button onClick={handleShuffle} style={{ display: "flex" }}>
|
||||
🔄
|
||||
</Button>
|
||||
</div>
|
||||
<div className="imageGallery" >{steps}</div>
|
||||
</div >)
|
||||
;
|
||||
};
|
|
@ -3,15 +3,20 @@
|
|||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
@ -36,7 +41,8 @@
|
|||
"controls"
|
||||
"player"
|
||||
"player-text";
|
||||
@media screen and (min-width: 768px){
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
grid-template-columns: 2fr 2.5fr;
|
||||
grid-template-rows: min-content min-content min-content 1fr;
|
||||
gap: 30px 30px;
|
||||
|
@ -55,10 +61,15 @@
|
|||
max-width: 450px;
|
||||
}
|
||||
|
||||
.presentation > p {
|
||||
.presentation>p {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
.gallery {
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
.cute-words {
|
||||
color: #54e8b3;
|
||||
}
|
||||
|
@ -73,7 +84,9 @@
|
|||
}
|
||||
|
||||
|
||||
.controls { grid-area: controls; }
|
||||
.controls {
|
||||
grid-area: controls;
|
||||
}
|
||||
|
||||
.player {
|
||||
grid-area: player;
|
||||
|
@ -88,12 +101,29 @@
|
|||
/* margin:auto; */
|
||||
}
|
||||
|
||||
.server-audio { grid-area: server-audio; }
|
||||
.server-audio {
|
||||
grid-area: server-audio;
|
||||
}
|
||||
|
||||
.user-audio { grid-area: user-audio; }
|
||||
.user-audio {
|
||||
grid-area: user-audio;
|
||||
}
|
||||
|
||||
.player-stats { grid-area: player-stats; width: 100%; height:100%; }
|
||||
.player-stats {
|
||||
grid-area: player-stats;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.commands { grid-area: commands; width: 100%; height:100%;}
|
||||
.commands {
|
||||
grid-area: commands;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.player-text { grid-area: player-text; width: 100%; height:100%; overflow:scroll;}
|
||||
.player-text {
|
||||
grid-area: player-text;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
|
@ -49,10 +49,10 @@ const buildURL = ({
|
|||
}
|
||||
const wsProtocol = (window.location.protocol === 'https:') ? 'wss' : 'ws';
|
||||
const url = new URL(`${wsProtocol}://${workerAddr}/api/chat`);
|
||||
if(workerAuthId) {
|
||||
if (workerAuthId) {
|
||||
url.searchParams.append("worker_auth_id", workerAuthId);
|
||||
}
|
||||
if(email) {
|
||||
if (email) {
|
||||
url.searchParams.append("email", email);
|
||||
}
|
||||
url.searchParams.append("text_temperature", params.textTemperature.toString());
|
||||
|
@ -64,12 +64,17 @@ const buildURL = ({
|
|||
url.searchParams.append("audio_seed", audioSeed.toString());
|
||||
url.searchParams.append("repetition_penalty_context", params.repetitionPenaltyContext.toString());
|
||||
url.searchParams.append("repetition_penalty", params.repetitionPenalty.toString());
|
||||
// Add image params if given
|
||||
if (params.imageUrl != undefined) {
|
||||
url.searchParams.append("image_url", params.imageUrl.toString());
|
||||
url.searchParams.append("image_resolution", params.imageResolution.toString());
|
||||
}
|
||||
console.log(url.toString());
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
|
||||
export const Conversation:FC<ConversationProps> = ({
|
||||
export const Conversation: FC<ConversationProps> = ({
|
||||
workerAddr,
|
||||
workerAuthId,
|
||||
audioContext,
|
||||
|
@ -77,7 +82,7 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
sessionAuthId,
|
||||
sessionId,
|
||||
onConversationEnd,
|
||||
isBypass=false,
|
||||
isBypass = false,
|
||||
email,
|
||||
...params
|
||||
}) => {
|
||||
|
@ -95,7 +100,7 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
|
||||
const audioStreamDestination = useRef<MediaStreamAudioDestinationNode>(audioContext.current.createMediaStreamDestination());
|
||||
const mediaRecorder = useRef<MediaRecorder | null>(null);
|
||||
const audioRecorder = useRef<MediaRecorder>(new MediaRecorder(audioStreamDestination.current.stream, { mimeType: getMimeType("audio"), audioBitsPerSecond: 128000 }));
|
||||
const audioRecorder = useRef<MediaRecorder>(new MediaRecorder(audioStreamDestination.current.stream, { mimeType: getMimeType("audio"), audioBitsPerSecond: 128000 }));
|
||||
const [videoURL, setVideoURL] = useState<string>("");
|
||||
const [audioURL, setAudioURL] = useState<string>("");
|
||||
const [isOver, setIsOver] = useState(false);
|
||||
|
@ -136,10 +141,10 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
audioRecorder.current.onstop = async () => {
|
||||
let blob: Blob;
|
||||
const mimeType = getMimeType("audio");
|
||||
if(mimeType.includes("webm")) {
|
||||
if (mimeType.includes("webm")) {
|
||||
blob = await fixWebmDuration(new Blob(audioChunks.current, { type: mimeType }));
|
||||
} else {
|
||||
blob = new Blob(audioChunks.current, { type: mimeType });
|
||||
} else {
|
||||
blob = new Blob(audioChunks.current, { type: mimeType });
|
||||
}
|
||||
setAudioURL(URL.createObjectURL(blob));
|
||||
audioChunks.current = [];
|
||||
|
@ -157,30 +162,30 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
|
||||
useEffect(() => {
|
||||
|
||||
if(!canvasRef) {
|
||||
if (!canvasRef) {
|
||||
console.log("No canvas ref");
|
||||
return;
|
||||
}
|
||||
if(!logoRef) {
|
||||
if (!logoRef) {
|
||||
console.log("No logo ref");
|
||||
return;
|
||||
}
|
||||
if(!isLogoLoaded) {
|
||||
if (!isLogoLoaded) {
|
||||
console.log("Logo not loaded");
|
||||
return;
|
||||
}
|
||||
if(!canvasRef.current) {
|
||||
if (!canvasRef.current) {
|
||||
console.log("No canvas");
|
||||
return;
|
||||
}
|
||||
if(!logoRef.current) {
|
||||
if (!logoRef.current) {
|
||||
console.log("No logo");
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = canvasRef.current.getContext("2d");
|
||||
if(ctx) {
|
||||
ctx.drawImage(logoRef.current, 20, 250 , 320, 98);
|
||||
if (ctx) {
|
||||
ctx.drawImage(logoRef.current, 20, 250, 320, 98);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.strokeRect(5, 5, 370, 370);
|
||||
|
@ -188,12 +193,12 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
}, [canvasRef, logoRef, isLogoLoaded]);
|
||||
|
||||
const startRecording = useCallback(() => {
|
||||
if(isRecording.current) {
|
||||
if (isRecording.current) {
|
||||
return;
|
||||
}
|
||||
console.log(Date.now() % 1000, "Starting recording");
|
||||
console.log("Starting recording");
|
||||
if(canvasRef.current) {
|
||||
if (canvasRef.current) {
|
||||
// Note: Attaching a track from this stream to the existing MediaRecorder
|
||||
// rather than creating a new MediaRecorder for the canvas stream
|
||||
// doesn't work on Safari as it just ends the recording immediately.
|
||||
|
@ -201,7 +206,7 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
console.log("Adding canvas to stream");
|
||||
const captureStream = canvasRef.current.captureStream(30);
|
||||
captureStream.addTrack(audioStreamDestination.current.stream.getAudioTracks()[0]);
|
||||
mediaRecorder.current = new MediaRecorder(captureStream, { mimeType: getMimeType("video"), videoBitsPerSecond: 1000000});
|
||||
mediaRecorder.current = new MediaRecorder(captureStream, { mimeType: getMimeType("video"), videoBitsPerSecond: 1000000 });
|
||||
mediaRecorder.current.ondataavailable = (e) => {
|
||||
console.log("Video data available");
|
||||
videoChunks.current.push(e.data);
|
||||
|
@ -209,7 +214,7 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
mediaRecorder.current.onstop = async () => {
|
||||
let blob: Blob;
|
||||
const mimeType = getMimeType("video");
|
||||
if(mimeType.includes("webm")) {
|
||||
if (mimeType.includes("webm")) {
|
||||
blob = await fixWebmDuration(new Blob(videoChunks.current, { type: mimeType }));
|
||||
} else {
|
||||
blob = new Blob(videoChunks.current, { type: mimeType });
|
||||
|
@ -232,7 +237,7 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
const stopRecording = useCallback(() => {
|
||||
console.log("Stopping recording");
|
||||
console.log("isRecording", isRecording)
|
||||
if(!isRecording.current) {
|
||||
if (!isRecording.current) {
|
||||
return;
|
||||
}
|
||||
worklet.current?.disconnect(audioStreamDestination.current);
|
||||
|
@ -250,82 +255,103 @@ export const Conversation:FC<ConversationProps> = ({
|
|||
socket,
|
||||
}}
|
||||
>
|
||||
<MediaContext.Provider value={
|
||||
{
|
||||
startRecording,
|
||||
stopRecording,
|
||||
audioContext,
|
||||
worklet,
|
||||
audioStreamDestination,
|
||||
micDuration,
|
||||
actualAudioPlayed,
|
||||
}
|
||||
}>
|
||||
<div>
|
||||
<div className="main-grid h-screen max-h-screen w-screen p-4 max-w-96 md:max-w-screen-lg m-auto">
|
||||
<div className="controls text-center flex justify-center items-center gap-2">
|
||||
{isOver && !isBypass && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
// Reload the page to reset the conversation on iOS
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
if(onConversationEnd && !isIOS) {
|
||||
onConversationEnd();
|
||||
return;
|
||||
}
|
||||
document.location.reload();
|
||||
}}
|
||||
>
|
||||
Start Over
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<MediaContext.Provider value={
|
||||
{
|
||||
(!isOver || isBypass) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
audioContext.current.resume();
|
||||
isConnected ? stop() : start();
|
||||
}}
|
||||
>
|
||||
{!isConnected ? "Connect" : "Disconnect"}
|
||||
</Button>
|
||||
)
|
||||
startRecording,
|
||||
stopRecording,
|
||||
audioContext,
|
||||
worklet,
|
||||
audioStreamDestination,
|
||||
micDuration,
|
||||
actualAudioPlayed,
|
||||
}
|
||||
<div className={`h-4 w-4 rounded-full ${isConnected ? 'bg-green-700': 'bg-red-700'}`} />
|
||||
</div>
|
||||
<div className="relative player h-full max-h-full w-full justify-between gap-3 border-2 border-white md:p-12">
|
||||
}>
|
||||
<div>
|
||||
<div className="main-grid h-screen max-h-screen w-screen p-4 max-w-96 md:max-w-screen-lg m-auto">
|
||||
<div className="controls text-center flex justify-center items-center gap-2">
|
||||
{isOver && !isBypass && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
// Reload the page to reset the conversation on iOS
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
if (onConversationEnd && !isIOS) {
|
||||
onConversationEnd();
|
||||
return;
|
||||
}
|
||||
localStorage.setItem("textTemperature", modelParams.textTemperature.toString());
|
||||
localStorage.setItem("textTopk", modelParams.textTopk.toString());
|
||||
localStorage.setItem("audioTemperature", modelParams.audioTemperature.toString());
|
||||
localStorage.setItem("audioTopk", modelParams.audioTopk.toString());
|
||||
localStorage.setItem("padMult", modelParams.padMult.toString());
|
||||
localStorage.setItem("repetitionPenalty", modelParams.repetitionPenalty.toString());
|
||||
localStorage.setItem("repetitionPenaltyContext", modelParams.repetitionPenaltyContext.toString());
|
||||
localStorage.setItem("imageResolution", modelParams.imageResolution.toString());
|
||||
localStorage.setItem("isImageMode", (modelParams.imageUrl != undefined).toString());
|
||||
document.location.reload();
|
||||
}}
|
||||
>
|
||||
Start Over
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
{
|
||||
(!isOver || isBypass) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
audioContext.current.resume();
|
||||
isConnected ? stop() : start();
|
||||
}}
|
||||
>
|
||||
{!isConnected ? "Connect" : "Disconnect"}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<div className={`h-4 w-4 rounded-full ${isConnected ? 'bg-green-700' : 'bg-red-700'}`} />
|
||||
</div>
|
||||
<div className="relative player h-full max-h-full w-full justify-between gap-3 border-2 border-white md:p-12"
|
||||
style={{
|
||||
backgroundImage: `url(${params.imageUrl})`,
|
||||
backgroundSize: '70%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center 10%',
|
||||
}} >
|
||||
<ServerAudio
|
||||
imageUrl={params.imageUrl}
|
||||
copyCanvasRef={canvasRef}
|
||||
setGetAudioStats={(callback: () => AudioStats) =>
|
||||
(getAudioStats.current = callback)
|
||||
}
|
||||
/>
|
||||
<UserAudio copyCanvasRef={canvasRef}/>
|
||||
<div className="pt-8 text-sm flex justify-center items-center flex-col download-links">
|
||||
<UserAudio copyCanvasRef={canvasRef} />
|
||||
<div className="pt-8 text-sm flex justify-center items-center flex-col download-links"
|
||||
style={{
|
||||
minHeight: 80,
|
||||
margin: -10,
|
||||
padding: 0,
|
||||
}}>
|
||||
{audioURL && <div><a href={audioURL} download={`moshi audio.${getExtension("audio")}`} className="pt-2 text-center block">Download audio</a></div>}
|
||||
{videoURL && <div><a href={videoURL} download={`moshi video.${getExtension("video")}`} className="pt-2 text-center">Download video</a></div>}
|
||||
{videoURL && getExtension("video") === "webm" && <div><a href="https://restream.io/tools/webm-to-mp4-converter" target="_blank" rel="noreferrer" className="explain-links pt-2 text-center italic block">How to convert to mp4</a></div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="scrollbar player-text border-2 border-white " ref={textContainerRef}>
|
||||
<TextDisplay containerRef={textContainerRef} displayColor={params.displayColor} />
|
||||
</div>
|
||||
<div className="player-stats hidden md:block">
|
||||
<ServerAudioStats getAudioStats={getAudioStats} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="scrollbar player-text border-2 border-white " ref={textContainerRef}>
|
||||
<TextDisplay containerRef={textContainerRef}/>
|
||||
</div>
|
||||
<div className="player-stats hidden md:block">
|
||||
<ServerAudioStats getAudioStats={getAudioStats} />
|
||||
<div className="max-w-96 md:max-w-screen-lg p-4 m-auto text-center">
|
||||
<ServerInfo />
|
||||
{!workerAuthId && <ModelParams {...modelParams} isConnected={isConnected} isImageMode={params.imageUrl != undefined} />}
|
||||
</div>
|
||||
<canvas height={380} width={380} className="hidden" ref={canvasRef} />
|
||||
<img src={canvasLogo} ref={logoRef} className="hidden" onLoad={() => {
|
||||
console.log("Logo loaded");
|
||||
setIsLogoLoaded(true);
|
||||
}} />
|
||||
</div>
|
||||
<div className="max-w-96 md:max-w-screen-lg p-4 m-auto text-center">
|
||||
<ServerInfo/>
|
||||
{!workerAuthId && <ModelParams {...modelParams} isConnected={isConnected} /> }
|
||||
</div>
|
||||
<canvas height={380} width={380} className="hidden" ref={canvasRef} />
|
||||
<img src={canvasLogo} ref={logoRef} className="hidden" onLoad={() => {
|
||||
console.log("Logo loaded");
|
||||
setIsLogoLoaded(true);
|
||||
}} />
|
||||
</div>
|
||||
</MediaContext.Provider>
|
||||
</SocketContext.Provider>
|
||||
</SocketContext.Provider >
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ const COLORS = [
|
|||
];
|
||||
|
||||
export const ClientVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, copyCanvasRef }) => {
|
||||
const [canvasWidth, setCanvasWidth] = useState(parent.current ? Math.min(parent.current.clientWidth, parent.current.clientHeight) : 0 );
|
||||
const [canvasWidth, setCanvasWidth] = useState(parent.current ? Math.min(parent.current.clientWidth, parent.current.clientHeight) : 0);
|
||||
const requestRef = useRef<number | null>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
|
@ -40,11 +40,11 @@ export const ClientVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
) => {
|
||||
const barHeight = height / 10 - gap;
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const barY = y + height + gap + Math.min(1, width / 30)- (i * barHeight + i * gap);
|
||||
const barY = y + height + gap + Math.min(1, width / 30) - (i * barHeight + i * gap);
|
||||
ctx.fillStyle = COLORS[i - 1];
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.lineWidth = Math.min(1, height / 100);
|
||||
if(i <= volume) {
|
||||
if (i <= volume) {
|
||||
ctx.fillRect(x, barY, width, barHeight);
|
||||
}
|
||||
ctx.strokeRect(x, barY, width, barHeight);
|
||||
|
@ -53,7 +53,7 @@ export const ClientVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
[],
|
||||
);
|
||||
|
||||
const draw = useCallback((ctx:CanvasRenderingContext2D, audioData: Uint8Array, x:number, y: number, width:number, height: number) => {
|
||||
const draw = useCallback((ctx: CanvasRenderingContext2D, audioData: Uint8Array, x: number, y: number, width: number, height: number) => {
|
||||
const stereoGap = Math.floor(width / 30);
|
||||
const barGap = Math.floor(height / 30);
|
||||
const padding = Math.floor(width / 30);
|
||||
|
@ -72,7 +72,7 @@ export const ClientVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
MAX_INTENSITY,
|
||||
);
|
||||
const volume = Math.floor((intensity * 10) / MAX_INTENSITY);
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0)";
|
||||
ctx.fillRect(x, y, width, height);
|
||||
drawBars(
|
||||
ctx,
|
||||
|
@ -96,7 +96,7 @@ export const ClientVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
|
||||
const visualizeData = useCallback(() => {
|
||||
const width = parent.current ? Math.min(parent.current.clientWidth, parent.current.clientHeight) : 0
|
||||
if(width !== canvasWidth) {
|
||||
if (width !== canvasWidth) {
|
||||
console.log("Setting canvas width");
|
||||
setCanvasWidth(width);
|
||||
}
|
||||
|
@ -114,10 +114,10 @@ export const ClientVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
return;
|
||||
}
|
||||
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
||||
draw(ctx, audioData, 0, 0, width, width);
|
||||
if(copyCanvasRef?.current) {
|
||||
draw(ctx, audioData, 0, 0, width, width);
|
||||
if (copyCanvasRef?.current) {
|
||||
const copyCtx = copyCanvasRef.current.getContext("2d");
|
||||
if(copyCtx) {
|
||||
if (copyCtx) {
|
||||
copyCtx.clearRect(220, 40, 140, 180);
|
||||
draw(copyCtx, audioData, 220, 40, 140, 180);
|
||||
}
|
||||
|
|
|
@ -5,19 +5,21 @@ import { useSocketContext } from "../../SocketContext";
|
|||
type AudioVisualizerProps = {
|
||||
analyser: AnalyserNode | null;
|
||||
parent: RefObject<HTMLElement>;
|
||||
imageUrl: string | undefined;
|
||||
copyCanvasRef?: RefObject<HTMLCanvasElement>;
|
||||
};
|
||||
|
||||
const MAX_INTENSITY = 255;
|
||||
|
||||
export const ServerVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, copyCanvasRef }) => {
|
||||
const [canvasWidth, setCanvasWidth] = useState( parent.current ? Math.min(parent.current.clientWidth, parent.current.clientHeight) : 0 );
|
||||
export const ServerVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, imageUrl, copyCanvasRef }) => {
|
||||
const [canvasWidth, setCanvasWidth] = useState(parent.current ? Math.min(parent.current.clientWidth, parent.current.clientHeight) : 0);
|
||||
const requestRef = useRef<number | null>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const { isConnected } = useSocketContext();
|
||||
|
||||
const draw = useCallback((width: number, centerX:number, centerY:number,audioData: Uint8Array, ctx: CanvasRenderingContext2D) => {
|
||||
|
||||
const draw = useCallback((width: number, centerX: number, centerY: number, audioData: Uint8Array, imageUrl: string | undefined, ctx: CanvasRenderingContext2D) => {
|
||||
const maxCircleWidth = Math.floor(width * 0.95);
|
||||
const averageIntensity = Math.sqrt(
|
||||
audioData.reduce((acc, curr) => acc + curr * curr, 0) / audioData.length,
|
||||
|
@ -30,11 +32,21 @@ export const ServerVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
const relIntensity = intensity / MAX_INTENSITY;
|
||||
const radius = ((isConnected ? 0.3 + 0.7 * relIntensity : relIntensity) * maxCircleWidth) / 2;
|
||||
// Draw a circle with radius based on intensity
|
||||
ctx.clearRect( centerX - width /2, centerY - width/2 , width, width);
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.fillRect(centerX - width / 2, centerY - width / 2, width, width);
|
||||
if (imageUrl == undefined) {
|
||||
ctx.clearRect(centerX - width / 2, centerY - width / 2, width, width);
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
|
||||
ctx.fillRect(centerX - width / 2, centerY - width / 2, width, width);
|
||||
} else {
|
||||
const img = new Image()
|
||||
img.src = imageUrl;
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, centerX - width / 2, centerY - width / 2, width, width);
|
||||
};
|
||||
console.log(img.src);
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = "#39e3a7";
|
||||
//ctx.fillStyle = "#39e3a7";
|
||||
ctx.fillStyle = 'rgba(57, 227, 167, 0.5)';
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
|
@ -43,7 +55,8 @@ export const ServerVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
if (isConnected) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, maxCircleWidth / 6, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = "#BCFCE5";
|
||||
// ctx.fillStyle = "#BCFCE5";
|
||||
ctx.fillStyle = 'rgba(188, 252, 229, 0.5)';
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
}
|
||||
|
@ -54,12 +67,14 @@ export const ServerVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
ctx.strokeStyle = "white";
|
||||
ctx.lineWidth = width / 50;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
|
||||
ctx.fill()
|
||||
ctx.closePath();
|
||||
}, [isConnected]);
|
||||
|
||||
const visualizeData = useCallback(() => {
|
||||
const width = parent.current ? Math.min(parent.current.clientWidth, parent.current.clientHeight) : 0;
|
||||
if(width !== canvasWidth) {
|
||||
if (width !== canvasWidth) {
|
||||
console.log("Setting canvas width");
|
||||
setCanvasWidth(width);
|
||||
}
|
||||
|
@ -71,18 +86,20 @@ export const ServerVisualizer: FC<AudioVisualizerProps> = ({ analyser, parent, c
|
|||
const ctx = canvasRef.current.getContext("2d");
|
||||
const audioData = new Uint8Array(140);
|
||||
analyser?.getByteFrequencyData(audioData);
|
||||
if(!ctx){
|
||||
if (!ctx) {
|
||||
console.log("Canvas context not found");
|
||||
return;
|
||||
}
|
||||
const centerX = width / 2;
|
||||
const centerY = width / 2;
|
||||
draw(width, centerX, centerY, audioData, ctx);
|
||||
if(copyCanvasRef?.current){
|
||||
// Hack: For the image, we display it using CSS background-image
|
||||
// in the main image, but we display it via canvas so that
|
||||
// it is in the video export
|
||||
draw(width, centerX, centerY, audioData, undefined, ctx);
|
||||
if (copyCanvasRef?.current) {
|
||||
const copyCtx = copyCanvasRef.current.getContext("2d");
|
||||
if(copyCtx){
|
||||
copyCtx.clearRect(50, 50, 150, 150);
|
||||
draw(150, 125, 125, audioData, copyCtx);
|
||||
if (copyCtx) {
|
||||
draw(150, 125, 125, audioData, imageUrl, copyCtx);
|
||||
}
|
||||
}
|
||||
}, [analyser, isConnected, canvasWidth, parent, copyCanvasRef]);
|
||||
|
|
|
@ -4,9 +4,10 @@ import { Button } from "../../../../components/Button/Button";
|
|||
|
||||
type ModelParamsProps = {
|
||||
isConnected: boolean;
|
||||
isImageMode: boolean;
|
||||
modal?: RefObject<HTMLDialogElement>,
|
||||
} & ReturnType<typeof useModelParams>;
|
||||
export const ModelParams:FC<ModelParamsProps> = ({
|
||||
} & ReturnType<typeof useModelParams>;
|
||||
export const ModelParams: FC<ModelParamsProps> = ({
|
||||
textTemperature,
|
||||
textTopk,
|
||||
audioTemperature,
|
||||
|
@ -14,6 +15,7 @@ export const ModelParams:FC<ModelParamsProps> = ({
|
|||
padMult,
|
||||
repetitionPenalty,
|
||||
repetitionPenaltyContext,
|
||||
imageResolution,
|
||||
setTextTemperature,
|
||||
setTextTopk,
|
||||
setAudioTemperature,
|
||||
|
@ -21,55 +23,64 @@ export const ModelParams:FC<ModelParamsProps> = ({
|
|||
setPadMult,
|
||||
setRepetitionPenalty,
|
||||
setRepetitionPenaltyContext,
|
||||
setImageResolution,
|
||||
resetParams,
|
||||
isConnected,
|
||||
isImageMode,
|
||||
modal,
|
||||
}) => {
|
||||
return (
|
||||
<div className=" p-2 mt-6 self-center flex flex-col text-white items-center text-center">
|
||||
<table>
|
||||
<tbody>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Text temperature:</td>
|
||||
<td className="w-12 text-center">{textTemperature}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="text-temperature" name="text-temperature" step="0.01" min="0.2" max="1.2" value={textTemperature} onChange={e => setTextTemperature(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Text topk:</td>
|
||||
<td className="w-12 text-center">{textTopk}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="text-topk" name="text-topk" step="1" min="10" max="500" value={textTopk} onChange={e => setTextTopk(parseInt(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Audio temperature:</td>
|
||||
<td className="w-12 text-center">{audioTemperature}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="audio-temperature" name="audio-temperature" step="0.01" min="0.2" max="1.2" value={audioTemperature} onChange={e => setAudioTemperature(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Audio topk:</td>
|
||||
<td className="w-12 text-center">{audioTopk}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="audio-topk" name="audio-topk" step="1" min="10" max="500" value={audioTopk} onChange={e => setAudioTopk(parseInt(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Padding multiplier:</td>
|
||||
<td className="w-12 text-center">{padMult}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="audio-pad-mult" name="audio-pad-mult" step="0.05" min="-4" max="4" value={padMult} onChange={e => setPadMult(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Repeat penalty:</td>
|
||||
<td className="w-12 text-center">{repetitionPenalty}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="repetition-penalty" name="repetition-penalty" step="0.01" min="1" max="2" value={repetitionPenalty} onChange={e => setRepetitionPenalty(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Repeat penalty last N:</td>
|
||||
<td className="w-12 text-center">{repetitionPenaltyContext}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="repetition-penalty-context" name="repetition-penalty-context" step="1" min="0" max="200" value={repetitionPenaltyContext} onChange={e => setRepetitionPenaltyContext(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
{isImageMode &&
|
||||
<tr>
|
||||
<td>Text temperature:</td>
|
||||
<td className="w-12 text-center">{textTemperature}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="text-temperature" name="text-temperature" step="0.01" min="0.2" max="1.2" value={textTemperature} onChange={e => setTextTemperature(parseFloat(e.target.value))} /></td>
|
||||
<td>Image max-side (px):</td>
|
||||
<td className="w-12 text-center">{imageResolution}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="image-resolution" name="image-resolution" step="16" min="64" max="512" value={imageResolution} onChange={e => setImageResolution(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Text topk:</td>
|
||||
<td className="w-12 text-center">{textTopk}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="text-topk" name="text-topk" step="1" min="10" max="500" value={textTopk} onChange={e => setTextTopk(parseInt(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Audio temperature:</td>
|
||||
<td className="w-12 text-center">{audioTemperature}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="audio-temperature" name="audio-temperature" step="0.01" min="0.2" max="1.2" value={audioTemperature} onChange={e => setAudioTemperature(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Audio topk:</td>
|
||||
<td className="w-12 text-center">{audioTopk}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="audio-topk" name="audio-topk" step="1" min="10" max="500" value={audioTopk} onChange={e => setAudioTopk(parseInt(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Padding multiplier:</td>
|
||||
<td className="w-12 text-center">{padMult}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="audio-pad-mult" name="audio-pad-mult" step="0.05" min="-4" max="4" value={padMult} onChange={e => setPadMult(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Repeat penalty:</td>
|
||||
<td className="w-12 text-center">{repetitionPenalty}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="repetition-penalty" name="repetition-penalty" step="0.01" min="1" max="2" value={repetitionPenalty} onChange={e => setRepetitionPenalty(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Repeat penalty last N:</td>
|
||||
<td className="w-12 text-center">{repetitionPenaltyContext}</td>
|
||||
<td className="p-2"><input className="range align-middle" disabled={isConnected} type="range" id="repetition-penalty-context" name="repetition-penalty-context" step="1" min="0" max="200" value={repetitionPenaltyContext} onChange={e => setRepetitionPenaltyContext(parseFloat(e.target.value))} /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<Button onClick={resetParams} className="m-2">Reset</Button>
|
||||
<Button onClick={() => modal?.current?.close()} className="m-2">Validate</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
{!isConnected && <Button onClick={resetParams} className="m-2">Reset</Button>}
|
||||
{!isConnected && <Button onClick={() => modal?.current?.close()} className="m-2">Validate</Button>}
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
};
|
||||
|
|
|
@ -4,9 +4,10 @@ import { ServerVisualizer } from "../AudioVisualizer/ServerVisualizer";
|
|||
|
||||
type ServerAudioProps = {
|
||||
setGetAudioStats: (getAudioStats: () => AudioStats) => void;
|
||||
imageUrl: string | undefined;
|
||||
copyCanvasRef?: React.RefObject<HTMLCanvasElement>;
|
||||
};
|
||||
export const ServerAudio: FC<ServerAudioProps> = ({ setGetAudioStats,copyCanvasRef }) => {
|
||||
export const ServerAudio: FC<ServerAudioProps> = ({ setGetAudioStats, imageUrl, copyCanvasRef }) => {
|
||||
const { analyser, hasCriticalDelay, setHasCriticalDelay } = useServerAudio({
|
||||
setGetAudioStats,
|
||||
});
|
||||
|
@ -27,7 +28,7 @@ export const ServerAudio: FC<ServerAudioProps> = ({ setGetAudioStats,copyCanvasR
|
|||
</div>
|
||||
)}
|
||||
<div className="server-audio h-4/6 aspect-square" ref={containerRef}>
|
||||
<ServerVisualizer analyser={analyser.current} parent={containerRef} copyCanvasRef={copyCanvasRef}/>
|
||||
<ServerVisualizer analyser={analyser.current} parent={containerRef} imageUrl={imageUrl} copyCanvasRef={copyCanvasRef} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -3,12 +3,28 @@ import { useServerText } from "../../hooks/useServerText";
|
|||
|
||||
type TextDisplayProps = {
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
displayColor: boolean | undefined;
|
||||
};
|
||||
|
||||
export const TextDisplay:FC<TextDisplayProps> = ({
|
||||
containerRef,
|
||||
// Palette 2: Purple to Green Moshi
|
||||
// sns.diverging_palette(288, 145, s=90, l=72, n=11)
|
||||
const textDisplayColors = [
|
||||
"#d19bf7", "#d7acf6", "#debdf5", "#e4cef4",
|
||||
"#ebe0f3", "#eef2f0", "#c8ead9", "#a4e2c4",
|
||||
"#80d9af", "#5bd09a", "#38c886"]
|
||||
|
||||
function clamp_color(v: number) {
|
||||
return v <= 0
|
||||
? 0
|
||||
: v >= textDisplayColors.length
|
||||
? textDisplayColors.length
|
||||
: v
|
||||
}
|
||||
|
||||
export const TextDisplay: FC<TextDisplayProps> = ({
|
||||
containerRef, displayColor
|
||||
}) => {
|
||||
const { text } = useServerText();
|
||||
const { text, textColor } = useServerText();
|
||||
const currentIndex = text.length - 1;
|
||||
const prevScrollTop = useRef(0);
|
||||
|
||||
|
@ -21,9 +37,27 @@ export const TextDisplay:FC<TextDisplayProps> = ({
|
|||
});
|
||||
}
|
||||
}, [text]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full max-w-full max-h-full p-2 text-white">
|
||||
if (displayColor && (textColor.length == text.length)) {
|
||||
return (
|
||||
<div className="h-full w-full max-w-full max-h-full p-2 text-white">
|
||||
{text.map((t, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={`${i === currentIndex ? "font-bold" : "font-normal"}`}
|
||||
style={{
|
||||
color: `${textDisplayColors[clamp_color(textColor[i])]}`
|
||||
}}
|
||||
>
|
||||
{t}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div className="h-full w-full max-w-full max-h-full p-2 text-white">
|
||||
{text.map((t, i) => (
|
||||
<span
|
||||
key={i}
|
||||
|
@ -32,6 +66,7 @@ export const TextDisplay:FC<TextDisplayProps> = ({
|
|||
{t}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ import { ClientVisualizer } from "../AudioVisualizer/ClientVisualizer";
|
|||
type UserAudioProps = {
|
||||
copyCanvasRef: React.RefObject<HTMLCanvasElement>;
|
||||
};
|
||||
export const UserAudio: FC<UserAudioProps> = ({copyCanvasRef}) => {
|
||||
export const UserAudio: FC<UserAudioProps> = ({ copyCanvasRef }) => {
|
||||
const [analyser, setAnalyser] = useState<AnalyserNode | null>(null);
|
||||
const { sendMessage, isConnected } = useSocketContext();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -65,7 +65,7 @@ export const UserAudio: FC<UserAudioProps> = ({copyCanvasRef}) => {
|
|||
|
||||
return (
|
||||
<div className="user-audio h-5/6 aspect-square" ref={containerRef}>
|
||||
<ClientVisualizer analyser={analyser} parent={containerRef} copyCanvasRef={copyCanvasRef}/>
|
||||
<ClientVisualizer analyser={analyser} parent={containerRef} copyCanvasRef={copyCanvasRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,10 @@ export const DEFAULT_AUDIO_TOPK = 250;
|
|||
export const DEFAULT_PAD_MULT = 0;
|
||||
export const DEFAULT_REPETITION_PENALTY_CONTEXT = 64;
|
||||
export const DEFAULT_REPETITION_PENALTY = 1.0;
|
||||
export const DEFAULT_IMAGE_RESOLUTION = 224;
|
||||
export const DEFAULT_IMAGE_URL = undefined;
|
||||
export const DEFAULT_IMAGE_MULT = 1.0;
|
||||
export const DEFAULT_DISPLAY_COLOR = false;
|
||||
|
||||
export type ModelParamsValues = {
|
||||
textTemperature: number;
|
||||
|
@ -16,19 +20,25 @@ export type ModelParamsValues = {
|
|||
padMult: number;
|
||||
repetitionPenaltyContext: number,
|
||||
repetitionPenalty: number,
|
||||
imageResolution: number,
|
||||
imageUrl: string | undefined,
|
||||
displayColor: boolean,
|
||||
};
|
||||
|
||||
type useModelParamsArgs = Partial<ModelParamsValues>;
|
||||
|
||||
export const useModelParams = (params?:useModelParamsArgs) => {
|
||||
export const useModelParams = (params?: useModelParamsArgs) => {
|
||||
|
||||
const [textTemperature, setTextTemperatureBase] = useState(params?.textTemperature || DEFAULT_TEXT_TEMPERATURE);
|
||||
const [textTopk, setTextTopkBase]= useState(params?.textTopk || DEFAULT_TEXT_TOPK);
|
||||
const [textTopk, setTextTopkBase] = useState(params?.textTopk || DEFAULT_TEXT_TOPK);
|
||||
const [audioTemperature, setAudioTemperatureBase] = useState(params?.audioTemperature || DEFAULT_AUDIO_TEMPERATURE);
|
||||
const [audioTopk, setAudioTopkBase] = useState(params?.audioTopk || DEFAULT_AUDIO_TOPK);
|
||||
const [padMult, setPadMultBase] = useState(params?.padMult || DEFAULT_PAD_MULT);
|
||||
const [repetitionPenalty, setRepetitionPenaltyBase] = useState(params?.repetitionPenalty || DEFAULT_REPETITION_PENALTY);
|
||||
const [repetitionPenaltyContext, setRepetitionPenaltyContextBase] = useState(params?.repetitionPenaltyContext || DEFAULT_REPETITION_PENALTY_CONTEXT);
|
||||
const [imageResolution, setImageResolutionBase] = useState(params?.imageResolution || DEFAULT_IMAGE_RESOLUTION);
|
||||
const [imageUrl, setImageUrlBase] = useState(params?.imageUrl || DEFAULT_IMAGE_URL);
|
||||
const [displayColor, setDisplayColorBase] = useState<boolean>(params?.displayColor == undefined ? DEFAULT_DISPLAY_COLOR : params?.displayColor);
|
||||
|
||||
const resetParams = useCallback(() => {
|
||||
setTextTemperatureBase(DEFAULT_TEXT_TEMPERATURE);
|
||||
|
@ -36,8 +46,11 @@ export const useModelParams = (params?:useModelParamsArgs) => {
|
|||
setAudioTemperatureBase(DEFAULT_AUDIO_TEMPERATURE);
|
||||
setAudioTopkBase(DEFAULT_AUDIO_TOPK);
|
||||
setPadMultBase(DEFAULT_PAD_MULT);
|
||||
setRepetitionPenalty(DEFAULT_REPETITION_PENALTY);
|
||||
setRepetitionPenaltyContext(DEFAULT_REPETITION_PENALTY_CONTEXT);
|
||||
setRepetitionPenaltyBase(DEFAULT_REPETITION_PENALTY);
|
||||
setRepetitionPenaltyContextBase(DEFAULT_REPETITION_PENALTY_CONTEXT);
|
||||
setImageResolutionBase(DEFAULT_IMAGE_RESOLUTION);
|
||||
setImageUrlBase(DEFAULT_IMAGE_URL);
|
||||
setDisplayColorBase(DEFAULT_DISPLAY_COLOR)
|
||||
}, [
|
||||
setTextTemperatureBase,
|
||||
setTextTopkBase,
|
||||
|
@ -46,44 +59,58 @@ export const useModelParams = (params?:useModelParamsArgs) => {
|
|||
setPadMultBase,
|
||||
setRepetitionPenaltyBase,
|
||||
setRepetitionPenaltyContextBase,
|
||||
setImageResolutionBase,
|
||||
setImageUrlBase,
|
||||
setDisplayColorBase
|
||||
]);
|
||||
|
||||
const setTextTemperature = useCallback((value: number) => {
|
||||
if(value <= 1.2 || value >= 0.2) {
|
||||
if (value <= 1.2 && value >= 0.2) {
|
||||
setTextTemperatureBase(value);
|
||||
}
|
||||
}, []);
|
||||
const setTextTopk = useCallback((value: number) => {
|
||||
if(value <= 500 || value >= 10) {
|
||||
if (value <= 500 && value >= 10) {
|
||||
setTextTopkBase(value);
|
||||
}
|
||||
}, []);
|
||||
const setAudioTemperature = useCallback((value: number) => {
|
||||
if(value <= 1.2 || value >= 0.2) {
|
||||
if (value <= 1.2 && value >= 0.2) {
|
||||
setAudioTemperatureBase(value);
|
||||
}
|
||||
}, []);
|
||||
const setAudioTopk = useCallback((value: number) => {
|
||||
if(value <= 500 || value >= 10) {
|
||||
if (value <= 500 && value >= 10) {
|
||||
setAudioTopkBase(value);
|
||||
}
|
||||
}, []);
|
||||
const setPadMult = useCallback((value: number) => {
|
||||
if(value <= 4 || value >= -4) {
|
||||
if (value <= 4 && value >= -4) {
|
||||
setPadMultBase(value);
|
||||
}
|
||||
}, []);
|
||||
const setRepetitionPenalty = useCallback((value: number) => {
|
||||
if(value <= 2.0 || value >= 1.0) {
|
||||
if (value <= 2.0 && value >= 1.0) {
|
||||
setRepetitionPenaltyBase(value);
|
||||
}
|
||||
}, []);
|
||||
const setRepetitionPenaltyContext = useCallback((value: number) => {
|
||||
if(value <= 200|| value >= 0) {
|
||||
if (value <= 200 && value >= 0) {
|
||||
setRepetitionPenaltyContextBase(value);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setImageResolution = useCallback((value: number) => {
|
||||
if (value <= 512 && value >= 64) {
|
||||
setImageResolutionBase(value);
|
||||
}
|
||||
}, []);
|
||||
const setImageUrl = useCallback((value: string | undefined) => {
|
||||
// TODO(amelie): Maybe check whether path exists ?
|
||||
setImageUrlBase(value);
|
||||
}, []);
|
||||
const setDisplayColor = useCallback((value: boolean) => {
|
||||
setDisplayColorBase(value);
|
||||
}, []);
|
||||
return {
|
||||
textTemperature,
|
||||
textTopk,
|
||||
|
@ -92,6 +119,9 @@ export const useModelParams = (params?:useModelParamsArgs) => {
|
|||
padMult,
|
||||
repetitionPenalty,
|
||||
repetitionPenaltyContext,
|
||||
imageResolution,
|
||||
imageUrl,
|
||||
displayColor,
|
||||
setTextTemperature,
|
||||
setTextTopk,
|
||||
setAudioTemperature,
|
||||
|
@ -99,6 +129,9 @@ export const useModelParams = (params?:useModelParamsArgs) => {
|
|||
setPadMult,
|
||||
setRepetitionPenalty,
|
||||
setRepetitionPenaltyContext,
|
||||
setImageUrl,
|
||||
setImageResolution,
|
||||
setDisplayColor,
|
||||
resetParams,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { decodeMessage } from "../../../protocol/encoder";
|
|||
|
||||
export const useServerText = () => {
|
||||
const [text, setText] = useState<string[]>([]);
|
||||
const [textColor, setTextColor] = useState<number[]>([]);
|
||||
const [totalTextMessages, setTotalTextMessages] = useState(0);
|
||||
const { socket } = useSocketContext();
|
||||
|
||||
|
@ -13,6 +14,10 @@ export const useServerText = () => {
|
|||
if (message.type === "text") {
|
||||
setText(text => [...text, message.data]);
|
||||
setTotalTextMessages(count => count + 1);
|
||||
} else if (message.type === "coloredtext") {
|
||||
setText(text => [...text, message.data]);
|
||||
setTextColor(textColor => [...textColor, message.color]);
|
||||
setTotalTextMessages(count => count + 1);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
@ -28,5 +33,5 @@ export const useServerText = () => {
|
|||
};
|
||||
}, [socket]);
|
||||
|
||||
return { text, totalTextMessages };
|
||||
return { text, textColor, totalTextMessages };
|
||||
};
|
||||
|
|
|
@ -3,29 +3,52 @@ import { FC, useEffect, useState, useCallback, useRef, MutableRefObject } from "
|
|||
import eruda from "eruda";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { Conversation } from "../Conversation/Conversation";
|
||||
import { Button } from "../../components/Button/Button";
|
||||
import { Button, SwitchButton } from "../../components/Button/Button";
|
||||
import { ImageGallery } from "../../components/ImageGallery/ImageGallery";
|
||||
import { useModelParams } from "../Conversation/hooks/useModelParams";
|
||||
import { ModelParams } from "../Conversation/components/ModelParams/ModelParams";
|
||||
import { env } from "../../env";
|
||||
|
||||
export const Queue:FC = () => {
|
||||
function getFloatFromStorage(val: string | null) {
|
||||
return (val == null) ? undefined : parseFloat(val)
|
||||
}
|
||||
|
||||
function getIntFromStorage(val: string | null) {
|
||||
return (val == null) ? undefined : parseInt(val)
|
||||
}
|
||||
function getBooleanFromStorage(val: string | null) {
|
||||
return (val == "true") ? true : ((val == "false") ? false : undefined)
|
||||
}
|
||||
|
||||
export const Queue: FC = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const overrideWorkerAddr = searchParams.get("worker_addr");
|
||||
const [hasMicrophoneAccess, setHasMicrophoneAccess] = useState<boolean>(false);
|
||||
const [showMicrophoneAccessMessage, setShowMicrophoneAccessMessage] = useState<boolean>(false);
|
||||
const [shouldConnect, setShouldConnect] = useState<boolean>(false);
|
||||
const modelParams = useModelParams();
|
||||
const startAsImage = getBooleanFromStorage(localStorage.getItem("isImageMode"));
|
||||
const [isImageMode, setisImageMode] = useState<boolean>(startAsImage == undefined ? false : startAsImage);
|
||||
const modelParams = useModelParams({
|
||||
textTemperature: getFloatFromStorage(localStorage.getItem("textTemperature")),
|
||||
textTopk: getIntFromStorage(localStorage.getItem("textTopk")),
|
||||
audioTemperature: getFloatFromStorage(localStorage.getItem("audioTemperature")),
|
||||
audioTopk: getIntFromStorage(localStorage.getItem("audioTopk")),
|
||||
padMult: getFloatFromStorage(localStorage.getItem("padMult")),
|
||||
repetitionPenalty: getFloatFromStorage(localStorage.getItem("repetitionPenalty")),
|
||||
repetitionPenaltyContext: getIntFromStorage(localStorage.getItem("repetitionPenaltyContext")),
|
||||
imageResolution: getIntFromStorage(localStorage.getItem("imageResolution"))
|
||||
});
|
||||
const modalRef = useRef<HTMLDialogElement>(null);
|
||||
|
||||
const audioContext = useRef<AudioContext | null>(null);
|
||||
const worklet = useRef<AudioWorkletNode | null>(null);
|
||||
// enable eruda in development
|
||||
useEffect(() => {
|
||||
if(env.VITE_ENV === "development") {
|
||||
if (env.VITE_ENV === "development") {
|
||||
eruda.init();
|
||||
}
|
||||
() => {
|
||||
if(env.VITE_ENV === "development") {
|
||||
if (env.VITE_ENV === "development") {
|
||||
eruda.destroy();
|
||||
}
|
||||
};
|
||||
|
@ -36,19 +59,19 @@ export const Queue:FC = () => {
|
|||
await window.navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
setHasMicrophoneAccess(true);
|
||||
return true;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setShowMicrophoneAccessMessage(true);
|
||||
setHasMicrophoneAccess(false);
|
||||
}
|
||||
return false;
|
||||
}, [setHasMicrophoneAccess, setShowMicrophoneAccessMessage]);
|
||||
}, [setHasMicrophoneAccess, setShowMicrophoneAccessMessage]);
|
||||
|
||||
const startProcessor = useCallback(async () => {
|
||||
if(!audioContext.current) {
|
||||
if (!audioContext.current) {
|
||||
audioContext.current = new AudioContext();
|
||||
}
|
||||
if(worklet.current) {
|
||||
if (worklet.current) {
|
||||
return;
|
||||
}
|
||||
let ctx = audioContext.current;
|
||||
|
@ -62,15 +85,15 @@ export const Queue:FC = () => {
|
|||
worklet.current.connect(ctx.destination);
|
||||
}, [audioContext, worklet]);
|
||||
|
||||
const onConnect = useCallback(async() => {
|
||||
await startProcessor();
|
||||
const hasAccess = await getMicrophoneAccess();
|
||||
if(hasAccess) {
|
||||
setShouldConnect(true);
|
||||
}
|
||||
const onConnect = useCallback(async () => {
|
||||
await startProcessor();
|
||||
const hasAccess = await getMicrophoneAccess();
|
||||
if (hasAccess) {
|
||||
setShouldConnect(true);
|
||||
}
|
||||
}, [setShouldConnect, startProcessor, getMicrophoneAccess]);
|
||||
|
||||
if(hasMicrophoneAccess && audioContext.current && worklet.current) {
|
||||
if (hasMicrophoneAccess && audioContext.current && worklet.current) {
|
||||
return (
|
||||
<Conversation
|
||||
workerAddr={overrideWorkerAddr ?? ""}
|
||||
|
@ -84,49 +107,57 @@ export const Queue:FC = () => {
|
|||
return (
|
||||
<div className="text-white text-center h-screen w-screen p-4 flex flex-col items-center ">
|
||||
<div>
|
||||
<h1 className="text-4xl">Moshi</h1>
|
||||
<h1 className="text-4xl" style={{ letterSpacing: isImageMode ? "2px" : "5px" }}>M{isImageMode ? "👁️" : "o"}shi</h1>
|
||||
<SwitchButton onClick={() => { setisImageMode(!isImageMode); modelParams.setImageUrl(undefined) }}>
|
||||
{isImageMode ? "Back to Moshi" : "Go to Moshi Vision"}
|
||||
</SwitchButton>
|
||||
{/*
|
||||
To add more space to the top add padding to the top of the following div
|
||||
by changing the pt-4 class to pt-8 or pt-12. (see: https://tailwindcss.com/docs/padding)
|
||||
If you'd like to move this part to the bottom of the screen, change the class to pb-4 or pb-8 and move the following so it is contained by the last one in the page.
|
||||
👁️ If you'd like to move this part to the bottom of the screen, change the class to pb-4 or pb-8 and move the following so it is contained by the last one in the page.
|
||||
Font size can be changed by changing the text-sm class to text-lg or text-xl. (see : https://tailwindcss.com/docs/font-size)
|
||||
As for the links you can use the one below as an example and add more by copying it and changing the href and text.
|
||||
*/}
|
||||
<div className="pt-8 text-sm flex justify-center items-center flex-col ">
|
||||
<div className="presentation text-left">
|
||||
<p><span className='cute-words'>Moshi</span> is an experimental conversational AI. </p>
|
||||
<p>Take everything it says with a grain of <span className='cute-words'>salt</span>.</p>
|
||||
<p>Conversations are limited to <span className='cute-words'>5 min</span>.</p>
|
||||
<p>Moshi <span className='cute-words'>thinks</span> and <span className='cute-words'>speaks</span> at the same time.</p>
|
||||
<p>Moshi can <span className='cute-words'>listen</span> and <span className='cute-words'>talk</span> at all time: <br/>maximum flow between you and <span className='cute-words'>Moshi</span>.</p>
|
||||
<p>Ask it to do some <span className='cute-words'>Pirate</span> role play, how to make <span className='cute-words'>Lasagna</span>,
|
||||
or what <span className='cute-words'>movie</span> it watched last.</p>
|
||||
<p>We strive to support all browsers, Chrome works best.</p>
|
||||
<p>Baked with <3 @<a href="https://kyutai.org/" className='cute-words underline'>Kyutai</a>.</p>
|
||||
<p><span className='cute-words'>Moshi</span> is an experimental conversational AI. </p>
|
||||
<p>Take everything it says with a grain of <span className='cute-words'>salt</span>.</p>
|
||||
<p>Conversations are limited to <span className='cute-words'>5 min</span>.</p>
|
||||
<p>Moshi <span className='cute-words'>thinks</span> and <span className='cute-words'>speaks</span> at the same time.</p>
|
||||
<p>Moshi can <span className='cute-words'>listen</span> and <span className='cute-words'>talk</span> at all time: <br />maximum flow between you and <span className='cute-words'>Moshi</span>.</p>
|
||||
<p>Ask it to do some <span className='cute-words'>Pirate</span> role play, how to make <span className='cute-words'>Lasagna</span>,
|
||||
or what <span className='cute-words'>movie</span> it watched last.</p>
|
||||
<p>We strive to support all browsers, Chrome works best.</p>
|
||||
<p>Baked with <3 @<a href="https://kyutai.org/" className='cute-words underline'>Kyutai</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-grow justify-center items-center flex-col presentation">
|
||||
{isImageMode ?
|
||||
<ImageGallery numImages={9} size={110} paramsSetter={modelParams.setImageUrl} clickAction={onConnect}></ImageGallery>
|
||||
:
|
||||
<Button onClick={async () => await onConnect()}>Connect</Button>}
|
||||
</div>
|
||||
<div className="flex flex-grow justify-center items-center flex-col">
|
||||
<>
|
||||
{showMicrophoneAccessMessage &&
|
||||
<p className="text-center">Please enable your microphone before proceeding</p>
|
||||
}
|
||||
<Button onClick={async () => await onConnect()}>Connect</Button>
|
||||
<Button className="absolute top-4 right-4" onClick={()=> modalRef.current?.showModal()}>Settings</Button>
|
||||
<dialog ref={modalRef} className="modal">
|
||||
<div className="modal-box border-2 border-white rounded-none flex justify-center bg-black">
|
||||
<ModelParams {...modelParams} isConnected={shouldConnect} modal={modalRef}/>
|
||||
</div>
|
||||
<form method="dialog" className="modal-backdrop">
|
||||
<button>Close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
<Button className="absolute top-4 right-4" onClick={() => modalRef.current?.showModal()}>Settings</Button>
|
||||
<dialog ref={modalRef} className="modal">
|
||||
<div className="modal-box border-2 border-white rounded-none flex justify-center bg-black">
|
||||
<ModelParams {...modelParams} isConnected={shouldConnect} isImageMode={isImageMode} modal={modalRef} />
|
||||
</div>
|
||||
<form method="dialog" className="modal-backdrop">
|
||||
<button>Close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</>
|
||||
</div>
|
||||
<div className="text-center flex justify-end items-center flex-col">
|
||||
<a target="_blank" href="https://kyutai.org/moshi-terms.pdf" className="text-center">Terms of Use</a>
|
||||
<a target="_blank" href="https://kyutai.org/moshi-privacy.pdf" className="text-center">Privacy Policy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
};
|
||||
|
|
|
@ -18,6 +18,8 @@ export const encodeMessage = (message: WSMessage): Uint8Array => {
|
|||
return new Uint8Array([0x01, ...message.data]);
|
||||
case "text":
|
||||
return new Uint8Array([0x02, ...new TextEncoder().encode(message.data)]);
|
||||
case "coloredtext":
|
||||
return new Uint8Array([0x02, 0x05, ...new TextEncoder().encode(message.data)]);
|
||||
case "control":
|
||||
return new Uint8Array([0x03, CONTROL_MESSAGES_MAP[message.action]]);
|
||||
case "metadata":
|
||||
|
@ -53,6 +55,12 @@ export const decodeMessage = (data: Uint8Array): WSMessage => {
|
|||
type: "text",
|
||||
data: new TextDecoder().decode(payload),
|
||||
};
|
||||
case 0x07:
|
||||
return {
|
||||
type: "coloredtext",
|
||||
color: payload[0],
|
||||
data: new TextDecoder().decode(payload.slice(1)),
|
||||
};
|
||||
case 0x03: {
|
||||
const action = Object.keys(CONTROL_MESSAGES_MAP).find(
|
||||
key => CONTROL_MESSAGES_MAP[key as CONTROL_MESSAGE] === payload[0],
|
||||
|
|
|
@ -2,6 +2,7 @@ export type MessageType =
|
|||
| "handshake"
|
||||
| "audio"
|
||||
| "text"
|
||||
| "coloredtext"
|
||||
| "control"
|
||||
| "metadata";
|
||||
|
||||
|
@ -19,32 +20,37 @@ export type MODEL = keyof typeof MODELS_MAP;
|
|||
|
||||
export type WSMessage =
|
||||
| {
|
||||
type: "handshake";
|
||||
version: VERSION;
|
||||
model: MODEL;
|
||||
}
|
||||
type: "handshake";
|
||||
version: VERSION;
|
||||
model: MODEL;
|
||||
}
|
||||
| {
|
||||
type: "audio";
|
||||
data: Uint8Array;
|
||||
}
|
||||
type: "audio";
|
||||
data: Uint8Array;
|
||||
}
|
||||
| {
|
||||
type: "text";
|
||||
data: string;
|
||||
}
|
||||
type: "text";
|
||||
data: string;
|
||||
}
|
||||
| {
|
||||
type: "control";
|
||||
action: CONTROL_MESSAGE;
|
||||
}
|
||||
type: "coloredtext";
|
||||
color: number;
|
||||
data: string;
|
||||
}
|
||||
| {
|
||||
type: "metadata";
|
||||
data: unknown;
|
||||
}
|
||||
type: "control";
|
||||
action: CONTROL_MESSAGE;
|
||||
}
|
||||
| {
|
||||
type: "metadata";
|
||||
data: unknown;
|
||||
}
|
||||
| {
|
||||
type: "error";
|
||||
data: string;
|
||||
}
|
||||
| {
|
||||
type:"ping";
|
||||
type: "ping";
|
||||
}
|
||||
|
||||
export const CONTROL_MESSAGES_MAP = {
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist",
|
||||
/* Bundler mode */
|
||||
|
@ -13,13 +17,16 @@
|
|||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": ["vite/client"]
|
||||
"types": [
|
||||
"vite/client"
|
||||
]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { ProxyOptions, defineConfig, loadEnv } from "vite";
|
||||
import topLevelAwait from "vite-plugin-top-level-await";
|
||||
|
||||
export default defineConfig(({mode}) => {
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
const proxyConf:Record<string, string | ProxyOptions> = env.VITE_QUEUE_API_URL ? {
|
||||
const proxyConf: Record<string, string | ProxyOptions> = env.VITE_QUEUE_API_URL ? {
|
||||
"/api": {
|
||||
target: env.VITE_QUEUE_API_URL,
|
||||
changeOrigin: true,
|
||||
|
@ -16,7 +16,7 @@ export default defineConfig(({mode}) => {
|
|||
cert: "./cert.pem",
|
||||
key: "./key.pem",
|
||||
},
|
||||
proxy:{
|
||||
proxy: {
|
||||
...proxyConf,
|
||||
}
|
||||
},
|
||||
|
|