diff --git a/.env b/.env index 1265eede2f1349f95f3db3ada7bdae68ea0aa133..c14a1ae41b952cbcb807957619ca51a2c54e52c4 100644 --- a/.env +++ b/.env @@ -112,6 +112,7 @@ PARQUET_EXPORT_SECRET= RATE_LIMIT= # requests per minute MESSAGES_BEFORE_LOGIN=# how many messages a user can send in a conversation before having to login. set to 0 to force login right away +APP_BASE="" # base path of the app, e.g. /chat, left blank as default PUBLIC_APP_NAME=ChatUI # name used as title throughout the app PUBLIC_APP_ASSETS=chatui # used to find logos & favicons in static/$PUBLIC_APP_ASSETS PUBLIC_APP_COLOR=blue # can be any of tailwind colors: https://tailwindcss.com/docs/customizing-colors#default-color-palette @@ -126,4 +127,6 @@ EXPOSE_API=true # PUBLIC_APP_COLOR=yellow # PUBLIC_APP_DESCRIPTION="Making the community's best AI chat models available to everyone." # PUBLIC_APP_DATA_SHARING=1 -# PUBLIC_APP_DISCLAIMER=1 \ No newline at end of file +# PUBLIC_APP_DISCLAIMER=1 + +ENABLE_ASSISTANTS=false #set to true to enable assistants feature \ No newline at end of file diff --git a/.env.template b/.env.template index d8573a499c7af9e782dc1e23d43536d3a08dfcd6..8fe36ccac7129d69d95742a4f3567b83d6beb7e6 100644 --- a/.env.template +++ b/.env.template @@ -254,4 +254,5 @@ PUBLIC_GOOGLE_ANALYTICS_ID=G-8Q63TH4CSL # ADDRESS_HEADER=X-Forwarded-For # XFF_DEPTH=2 -EXPOSE_API=false \ No newline at end of file +ENABLE_ASSISTANTS=true +EXPOSE_API=false diff --git a/.vscode/settings.json b/.vscode/settings.json index c32c1bbc3ef092e6702bb6ef83bf3fd87308d20b..0d24922796c50aa3b9c2007e1f04d02ad42174bf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll": "explicit" }, "eslint.validate": ["javascript", "svelte"] } diff --git a/package-lock.json b/package-lock.json index 23ca6bbbe3432f630b111e3242a365633b634ced..91688155b655aa66844b20097168b7f543446110 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@huggingface/hub": "^0.5.1", "@huggingface/inference": "^2.6.3", "@iconify-json/bi": "^1.1.21", + "@resvg/resvg-js": "^2.6.0", "@xenova/transformers": "^2.6.0", "autoprefixer": "^10.4.14", "browser-image-resizer": "^2.4.1", @@ -28,6 +29,8 @@ "parquetjs": "^0.11.2", "postcss": "^8.4.31", "saslprep": "^1.0.3", + "satori": "^0.10.11", + "satori-html": "^0.3.2", "serpapi": "^1.1.1", "tailwind-scrollbar": "^3.0.0", "tailwindcss": "^3.4.0", @@ -790,6 +793,208 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@resvg/resvg-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.0.tgz", + "integrity": "sha512-Tf3YpbBKcQn991KKcw/vg7vZf98v01seSv6CVxZBbRkL/xyjnoYB6KgrFL6zskT1A4dWC/vg77KyNOW+ePaNlA==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@resvg/resvg-js-android-arm-eabi": "2.6.0", + "@resvg/resvg-js-android-arm64": "2.6.0", + "@resvg/resvg-js-darwin-arm64": "2.6.0", + "@resvg/resvg-js-darwin-x64": "2.6.0", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.0", + "@resvg/resvg-js-linux-arm64-gnu": "2.6.0", + "@resvg/resvg-js-linux-arm64-musl": "2.6.0", + "@resvg/resvg-js-linux-x64-gnu": "2.6.0", + "@resvg/resvg-js-linux-x64-musl": "2.6.0", + "@resvg/resvg-js-win32-arm64-msvc": "2.6.0", + "@resvg/resvg-js-win32-ia32-msvc": "2.6.0", + "@resvg/resvg-js-win32-x64-msvc": "2.6.0" + } + }, + "node_modules/@resvg/resvg-js-android-arm-eabi": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.0.tgz", + "integrity": "sha512-lJnZ/2P5aMocrFMW7HWhVne5gH82I8xH6zsfH75MYr4+/JOaVcGCTEQ06XFohGMdYRP3v05SSPLPvTM/RHjxfA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.0.tgz", + "integrity": "sha512-N527f529bjMwYWShZYfBD60dXA4Fux+D695QsHQ93BDYZSHUoOh1CUGUyICevnTxs7VgEl98XpArmUWBZQVMfQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-MabUKLVayEwlPo0mIqAmMt+qESN8LltCvv5+GLgVga1avpUrkxj/fkU1TKm8kQegutUjbP/B0QuMuUr0uhF8ew==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.0.tgz", + "integrity": "sha512-zrFetdnSw/suXjmyxSjfDV7i61hahv6DDG6kM7BYN2yJ3Es5+BZtqYZTcIWogPJedYKmzN1YTMWGd/3f0ubFiA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.0.tgz", + "integrity": "sha512-sH4gxXt7v7dGwjGyzLwn7SFGvwZG6DQqLaZ11MmzbCwd9Zosy1TnmrMJfn6TJ7RHezmQMgBPi18bl55FZ1AT4A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.0.tgz", + "integrity": "sha512-fCyMncqCJtrlANADIduYF4IfnWQ295UKib7DAxFXQhBsM9PLDTpizr0qemZcCNadcwSVHnAIzL4tliZhCM8P6A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-ouLjTgBQHQyxLht4FdMPTvuY8xzJigM9EM2Tlu0llWkN1mKyTQrvYWi6TA6XnKdzDJHy7ZLpWpjZi7F5+Pg+Vg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-gnu": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.0.tgz", + "integrity": "sha512-n3zC8DWsvxC1AwxpKFclIPapDFibs5XdIRoV/mcIlxlh0vseW1F49b97F33BtJQRmlntsqqN6GMMqx8byB7B+Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-n4tasK1HOlAxdTEROgYA1aCfsEKk0UOFDNd/AQTTZlTmCbHKXPq+O8npaaKlwXquxlVK8vrkcWbksbiGqbCAcw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.0.tgz", + "integrity": "sha512-X2+EoBJFwDI5LDVb51Sk7ldnVLitMGr9WwU/i21i3fAeAXZb3hM16k67DeTy16OYkT2dk/RfU1tP1wG+rWbz2Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.0.tgz", + "integrity": "sha512-L7oevWjQoUgK5W1fCKn0euSVemhDXVhrjtwqpc7MwBKKimYeiOshO1Li1pa8bBt5PESahenhWgdB6lav9O0fEg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-x64-msvc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.0.tgz", + "integrity": "sha512-8lJlghb+Unki5AyKgsnFbRJwkEj9r1NpwyuBG8yEJiG1W9eEGl03R3I7bsVa3haof/3J1NlWf0rzSa1G++A2iw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@rollup/plugin-commonjs": { "version": "25.0.7", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz", @@ -922,6 +1127,21 @@ } } }, + "node_modules/@shuding/opentype.js": { + "version": "1.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", + "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==", + "dependencies": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/@sveltejs/adapter-node": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.3.1.tgz", @@ -1931,6 +2151,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001542", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001542.tgz", @@ -2190,6 +2418,34 @@ "node": "*" } }, + "node_modules/css-background-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz", + "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==" + }, + "node_modules/css-box-shadow": { + "version": "1.0.0-3", + "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", + "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==" + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", @@ -2472,6 +2728,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.359.tgz", "integrity": "sha512-OoVcngKCIuNXtZnsYoqlCvr0Cf3NIPzDIgwUfI9bdTFjXCrr79lI0kwQstLPZ7WhCezLlGksZk/BFAzoXC7GDw==" }, + "node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -2542,6 +2803,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2874,6 +3140,11 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3184,6 +3455,17 @@ "node": ">= 0.4" } }, + "node_modules/hex-rgb": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz", + "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/highlight.js": { "version": "11.7.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", @@ -3682,6 +3964,23 @@ "node": ">=10" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4417,6 +4716,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4457,6 +4761,15 @@ "node": ">=0.6.19" } }, + "node_modules/parse-css-color": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz", + "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==", + "dependencies": { + "color-name": "^1.1.4", + "hex-rgb": "^4.1.0" + } + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -5290,6 +5603,34 @@ "node": ">=6" } }, + "node_modules/satori": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.10.11.tgz", + "integrity": "sha512-yLm1xPRPZUaKcBZJ6nmezoJjHB4MqV8x7Mu0PyZUJodRWRDD27UbeMwzuY9LEGG57WYLO4CQsGPlbHWV1Ex9TQ==", + "dependencies": { + "@shuding/opentype.js": "1.4.0-beta.0", + "css-background-parser": "^0.1.0", + "css-box-shadow": "1.0.0-3", + "css-to-react-native": "^3.0.0", + "emoji-regex": "^10.2.1", + "escape-html": "^1.0.3", + "linebreak": "^1.1.0", + "parse-css-color": "^0.2.1", + "postcss-value-parser": "^4.2.0", + "yoga-wasm-web": "^0.3.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/satori-html": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/satori-html/-/satori-html-0.3.2.tgz", + "integrity": "sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==", + "dependencies": { + "ultrahtml": "^1.2.0" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -5553,6 +5894,11 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6054,6 +6400,11 @@ "globrex": "^0.1.2" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "node_modules/tinybench": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz", @@ -6270,6 +6621,11 @@ "node": ">=0.8.0" } }, + "node_modules/ultrahtml": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.2.tgz", + "integrity": "sha512-qh4mBffhlkiXwDAOxvSGxhL0QEQsTbnP9BozOK3OYPEGvPvdWzvAUaXNtUSMdNsKDtuyjEbyVUPFZ52SSLhLqw==" + }, "node_modules/undici": { "version": "5.26.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.4.tgz", @@ -6281,6 +6637,15 @@ "node": ">=14.0" } }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -6769,6 +7134,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==" + }, "node_modules/zod": { "version": "3.22.3", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", diff --git a/package.json b/package.json index 8d56863dfcddd0c0c73fd570264cf824f9bf3a87..5f2c4e86d1612d7c019d450815c2565299146e1c 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@huggingface/hub": "^0.5.1", "@huggingface/inference": "^2.6.3", "@iconify-json/bi": "^1.1.21", + "@resvg/resvg-js": "^2.6.0", "@xenova/transformers": "^2.6.0", "autoprefixer": "^10.4.14", "browser-image-resizer": "^2.4.1", @@ -64,6 +65,8 @@ "parquetjs": "^0.11.2", "postcss": "^8.4.31", "saslprep": "^1.0.3", + "satori": "^0.10.11", + "satori-html": "^0.3.2", "serpapi": "^1.1.1", "tailwind-scrollbar": "^3.0.0", "tailwindcss": "^3.4.0", diff --git a/src/lib/components/AssistantSettings.svelte b/src/lib/components/AssistantSettings.svelte new file mode 100644 index 0000000000000000000000000000000000000000..85394e8d6cf7d3550bbe6f540997d62da91fa244 --- /dev/null +++ b/src/lib/components/AssistantSettings.svelte @@ -0,0 +1,277 @@ + + +
{ + loading = true; + if (files?.[0] && files[0].size > 0 && compress) { + await compress(files[0], { + maxWidth: 500, + maxHeight: 500, + quality: 1, + }).then((resizedImage) => { + formData.set("avatar", resizedImage); + }); + } + + if (deleteExistingAvatar === true) { + if (assistant?.avatar) { + // if there is an avatar we explicitly removei t + formData.set("avatar", "null"); + } else { + // else we just remove it from the input + formData.delete("avatar"); + } + } + + return async ({ result }) => { + loading = false; + await applyAction(result); + }; + }} +> + {#if assistant} +

Edit assistant ({assistant?.name ?? ""})

+

+ Modifying an existing assistant will propagate those changes to all users. +

+ {:else} +

Create new assistant

+

+ Assistants are public, and can be accessed by anyone with the link. +

+ {/if} + +
+
+
+ Avatar + + + {#if (files && files[0]) || (assistant?.avatar && !deleteExistingAvatar)} +
+ {#if files && files[0]} + avatar + {:else if assistant?.avatar} + avatar + {/if} + + +
+
+ +
+ {:else} +
+ +
+

{getError("avatar", form)}

+ {/if} +
+ + + +
diff --git a/src/routes/settings/assistants/[assistantId]/+page.ts b/src/routes/settings/assistants/[assistantId]/+page.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d2ef393ee413ae0a534029dd25cfcf99cbd54cb --- /dev/null +++ b/src/routes/settings/assistants/[assistantId]/+page.ts @@ -0,0 +1,14 @@ +import { base } from "$app/paths"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ parent, params }) { + const data = await parent(); + + const assistant = data.settings.assistants.find((id) => id === params.assistantId); + + if (!assistant) { + throw redirect(302, `${base}/assistant/${params.assistantId}`); + } + + return data; +} diff --git a/src/routes/settings/assistants/[assistantId]/avatar/+server.ts b/src/routes/settings/assistants/[assistantId]/avatar/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b6be2c2cf63022a967d8435baa494d655ee4154 --- /dev/null +++ b/src/routes/settings/assistants/[assistantId]/avatar/+server.ts @@ -0,0 +1,46 @@ +import { collections } from "$lib/server/database"; +import { error, type RequestHandler } from "@sveltejs/kit"; +import { ObjectId } from "mongodb"; + +export const GET: RequestHandler = async ({ params }) => { + const assistant = await collections.assistants.findOne({ + _id: new ObjectId(params.assistantId), + }); + + if (!assistant) { + throw error(404, "No assistant found"); + } + + if (!assistant.avatar) { + throw error(404, "No avatar found"); + } + + const fileId = collections.bucket.find({ filename: assistant._id.toString() }); + + let mime = ""; + + const content = await fileId.next().then(async (file) => { + mime = file?.metadata?.mime; + + if (!file?._id) { + throw error(404, "Avatar not found"); + } + + const fileStream = collections.bucket.openDownloadStream(file?._id); + + const fileBuffer = await new Promise((resolve, reject) => { + const chunks: Uint8Array[] = []; + fileStream.on("data", (chunk) => chunks.push(chunk)); + fileStream.on("error", reject); + fileStream.on("end", () => resolve(Buffer.concat(chunks))); + }); + + return fileBuffer; + }); + + return new Response(content, { + headers: { + "Content-Type": mime ?? "application/octet-stream", + }, + }); +}; diff --git a/src/routes/settings/assistants/[assistantId]/edit/+page.server.ts b/src/routes/settings/assistants/[assistantId]/edit/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..22716f4a7644793b2c94ad86580088895a664fa4 --- /dev/null +++ b/src/routes/settings/assistants/[assistantId]/edit/+page.server.ts @@ -0,0 +1,136 @@ +import { base } from "$app/paths"; +import { requiresUser } from "$lib/server/auth"; +import { collections } from "$lib/server/database"; +import { fail, type Actions, redirect } from "@sveltejs/kit"; +import { ObjectId } from "mongodb"; + +import { z } from "zod"; +import sizeof from "image-size"; +import { sha256 } from "$lib/utils/sha256"; + +const newAsssistantSchema = z.object({ + name: z.string().min(1), + modelId: z.string().min(1), + preprompt: z.string().min(1), + description: z.string().optional(), + exampleInput1: z.string().optional(), + exampleInput2: z.string().optional(), + exampleInput3: z.string().optional(), + exampleInput4: z.string().optional(), + avatar: z.union([z.instanceof(File), z.literal("null")]).optional(), +}); + +const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise => { + const hash = await sha256(await avatar.text()); + const upload = collections.bucket.openUploadStream(`${assistantId.toString()}`, { + metadata: { type: avatar.type, hash }, + }); + + upload.write((await avatar.arrayBuffer()) as unknown as Buffer); + upload.end(); + + // only return the filename when upload throws a finish event or a 10s time out occurs + return new Promise((resolve, reject) => { + upload.once("finish", () => resolve(hash)); + upload.once("error", reject); + setTimeout(() => reject(new Error("Upload timed out")), 10000); + }); +}; + +export const actions: Actions = { + default: async ({ request, locals, params }) => { + const assistant = await collections.assistants.findOne({ + _id: new ObjectId(params.assistantId), + }); + + if (!assistant) { + throw Error("Assistant not found"); + } + + if (assistant.createdById.toString() !== (locals.user?._id ?? locals.sessionId).toString()) { + throw Error("You are not the author of this assistant"); + } + + const formData = Object.fromEntries(await request.formData()); + + const parse = newAsssistantSchema.safeParse(formData); + + if (!parse.success) { + // Loop through the errors array and create a custom errors array + const errors = parse.error.errors.map((error) => { + return { + field: error.path[0], + message: error.message, + }; + }); + + return fail(400, { error: true, errors }); + } + + // can only create assistants when logged in, IF login is setup + if (!locals.user && requiresUser) { + const errors = [{ field: "preprompt", message: "Must be logged in. Unauthorized" }]; + return fail(400, { error: true, errors }); + } + + const exampleInputs: string[] = [ + parse?.data?.exampleInput1 ?? "", + parse?.data?.exampleInput2 ?? "", + parse?.data?.exampleInput3 ?? "", + parse?.data?.exampleInput4 ?? "", + ].filter((input) => !!input); + + const deleteAvatar = parse.data.avatar === "null"; + + let hash; + if (parse.data.avatar && parse.data.avatar !== "null" && parse.data.avatar.size > 0) { + const dims = sizeof(Buffer.from(await parse.data.avatar.arrayBuffer())); + + if ((dims.height ?? 1000) > 512 || (dims.width ?? 1000) > 512) { + const errors = [{ field: "avatar", message: "Avatar too big" }]; + return fail(400, { error: true, errors }); + } + + const fileCursor = collections.bucket.find({ filename: assistant._id.toString() }); + + // Step 2: Delete the existing file if it exists + let fileId = await fileCursor.next(); + while (fileId) { + await collections.bucket.delete(fileId._id); + fileId = await fileCursor.next(); + } + + hash = await uploadAvatar(parse.data.avatar, assistant._id); + } else if (deleteAvatar) { + // delete the avatar + const fileCursor = collections.bucket.find({ filename: assistant._id.toString() }); + + let fileId = await fileCursor.next(); + while (fileId) { + await collections.bucket.delete(fileId._id); + fileId = await fileCursor.next(); + } + } + + const { acknowledged } = await collections.assistants.replaceOne( + { + _id: assistant._id, + }, + { + createdById: assistant?.createdById, + createdByName: locals.user?.username ?? locals.user?.name, + ...parse.data, + exampleInputs, + avatar: deleteAvatar ? undefined : hash ?? assistant.avatar, + createdAt: new Date(), + updatedAt: new Date(), + } + ); + + if (acknowledged) { + throw redirect(302, `${base}/settings/assistants/${assistant._id}`); + } else { + throw Error("Update failed"); + } + }, +}; diff --git a/src/routes/settings/assistants/[assistantId]/edit/+page.svelte b/src/routes/settings/assistants/[assistantId]/edit/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..682c29dfe02b840a5e8aff8067709e98ea46e285 --- /dev/null +++ b/src/routes/settings/assistants/[assistantId]/edit/+page.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/routes/settings/assistants/new/+page.server.ts b/src/routes/settings/assistants/new/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..bdb077c761a62f0de97e57a67c45e29a71bc4e55 --- /dev/null +++ b/src/routes/settings/assistants/new/+page.server.ts @@ -0,0 +1,112 @@ +import { base } from "$app/paths"; +import { authCondition, requiresUser } from "$lib/server/auth"; +import { collections } from "$lib/server/database"; +import { fail, type Actions, redirect } from "@sveltejs/kit"; +import { ObjectId } from "mongodb"; + +import { z } from "zod"; +import sizeof from "image-size"; +import { sha256 } from "$lib/utils/sha256"; + +const newAsssistantSchema = z.object({ + name: z.string().min(1), + modelId: z.string().min(1), + preprompt: z.string().min(1), + description: z.string().optional(), + exampleInput1: z.string().optional(), + exampleInput2: z.string().optional(), + exampleInput3: z.string().optional(), + exampleInput4: z.string().optional(), + avatar: z.instanceof(File).optional(), +}); + +const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise => { + const hash = await sha256(await avatar.text()); + const upload = collections.bucket.openUploadStream(`${assistantId.toString()}`, { + metadata: { type: avatar.type, hash }, + }); + + upload.write((await avatar.arrayBuffer()) as unknown as Buffer); + upload.end(); + + // only return the filename when upload throws a finish event or a 10s time out occurs + return new Promise((resolve, reject) => { + upload.once("finish", () => resolve(hash)); + upload.once("error", reject); + setTimeout(() => reject(new Error("Upload timed out")), 10000); + }); +}; + +export const actions: Actions = { + default: async ({ request, locals }) => { + const formData = Object.fromEntries(await request.formData()); + + const parse = newAsssistantSchema.safeParse(formData); + + if (!parse.success) { + // Loop through the errors array and create a custom errors array + const errors = parse.error.errors.map((error) => { + return { + field: error.path[0], + message: error.message, + }; + }); + + return fail(400, { error: true, errors }); + } + + // can only create assistants when logged in, IF login is setup + if (!locals.user && requiresUser) { + const errors = [{ field: "preprompt", message: "Must be logged in. Unauthorized" }]; + return fail(400, { error: true, errors }); + } + + const createdById = locals.user?._id ?? locals.sessionId; + + const newAssistantId = new ObjectId(); + + const exampleInputs: string[] = [ + parse?.data?.exampleInput1 ?? "", + parse?.data?.exampleInput2 ?? "", + parse?.data?.exampleInput3 ?? "", + parse?.data?.exampleInput4 ?? "", + ].filter((input) => !!input); + + let hash; + if (parse.data.avatar && parse.data.avatar.size > 0) { + const dims = sizeof(Buffer.from(await parse.data.avatar.arrayBuffer())); + + if ((dims.height ?? 1000) > 512 || (dims.width ?? 1000) > 512) { + const errors = [ + { + field: "avatar", + message: + "Avatar is too big. Please make sure the size of your avatar is no bigger than 512px by 512px.", + }, + ]; + return fail(400, { error: true, errors }); + } + + hash = await uploadAvatar(parse.data.avatar, newAssistantId); + } + + const { insertedId } = await collections.assistants.insertOne({ + _id: newAssistantId, + createdById, + createdByName: locals.user?.username ?? locals.user?.name, + ...parse.data, + exampleInputs, + avatar: hash, + createdAt: new Date(), + updatedAt: new Date(), + }); + + // add insertedId to user settings + + await collections.settings.updateOne(authCondition(locals), { + $push: { assistants: insertedId }, + }); + + throw redirect(302, `${base}/settings/assistants/${insertedId}`); + }, +}; diff --git a/src/routes/settings/assistants/new/+page.svelte b/src/routes/settings/assistants/new/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..e77cbfd26919d846978df0eaf68c2c7d86df9bcd --- /dev/null +++ b/src/routes/settings/assistants/new/+page.svelte @@ -0,0 +1,9 @@ + + + diff --git a/static/fonts/Inter-Black.ttf b/static/fonts/Inter-Black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b27822baea48062bf11617ce763e8d623c3a9769 Binary files /dev/null and b/static/fonts/Inter-Black.ttf differ diff --git a/static/fonts/Inter-Bold.ttf b/static/fonts/Inter-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fe23eeb9c93a377d0f4ab003f1f77b555d19b1d1 Binary files /dev/null and b/static/fonts/Inter-Bold.ttf differ diff --git a/static/fonts/Inter-ExtraBold.ttf b/static/fonts/Inter-ExtraBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..874b1b0dd7c63f46240530a710ccd503d58d866d Binary files /dev/null and b/static/fonts/Inter-ExtraBold.ttf differ diff --git a/static/fonts/Inter-ExtraLight.ttf b/static/fonts/Inter-ExtraLight.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c993e82216cd9b62f025c441fb65b7b7ccf2f96e Binary files /dev/null and b/static/fonts/Inter-ExtraLight.ttf differ diff --git a/static/fonts/Inter-Light.ttf b/static/fonts/Inter-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..71188f5cb2815547a5fb785f220910324ad8aa65 Binary files /dev/null and b/static/fonts/Inter-Light.ttf differ diff --git a/static/fonts/Inter-Medium.ttf b/static/fonts/Inter-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a01f3777a6fc284b7a720c0f8248a27066389ef9 Binary files /dev/null and b/static/fonts/Inter-Medium.ttf differ diff --git a/static/fonts/Inter-Regular.ttf b/static/fonts/Inter-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5e4851f0ab7e0268da6ce903306e2f871ee19821 Binary files /dev/null and b/static/fonts/Inter-Regular.ttf differ diff --git a/static/fonts/Inter-SemiBold.ttf b/static/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ecc7041e23ed1a61574efe48e8bc42441a401d6e Binary files /dev/null and b/static/fonts/Inter-SemiBold.ttf differ diff --git a/static/fonts/Inter-Thin.ttf b/static/fonts/Inter-Thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fe77243fc7ae5f860243c768fc7bc659839988a5 Binary files /dev/null and b/static/fonts/Inter-Thin.ttf differ