Assistants feature (#639)
Browse files* First push on assistants
* push fixes
* fix add assistant
* Sign up works
* lint
* mobile layout fixes
* design fixes
* Merge branch 'main' into feature/assistants
* fix copy button
* add error feedback
* hide duplicate feature
* remove wrong comments
* add autoredirect if assistant is missing
* latest changes:
- add edit feature
- hash assistant avatar
- get rid of ugly line
- check for non existent avatar
- make a better looking upload icon
* Update src/routes/conversation/+server.ts
Co-authored-by: Mishig <[email protected]>
* reused type more cleanly
* fix type in shared conversation
* fixed feature
* fix: share conv with an assistant
* delete assistant avatars in db when deleting avatar
* affordance on avatar upload
* improve assistant conv start on mobile
* settings modal fly in
* better mobile intro
* mobile padding
* link affordance
* Make assistants disabled by default, but enabled in huggingchat
* lint
* Fix bottom model name
* ui tweaks
* Initial work on chat thumbnails
* fix build
* Get rid of deps
* Update src/routes/settings/assistants/[assistantId]/avatar/+server.ts
Co-authored-by: Mishig <[email protected]>
* add comment to app_base
* Use event modifiers
* Use CSS uppercase instead everywhere
* Update src/lib/components/NavMenu.svelte
Co-authored-by: Mishig <[email protected]>
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <[email protected]>
* Clearer error message for avatar size check
* one less op on flag check
* revert back preventDefault change in LoginModal
* Update src/routes/settings/+layout.svelte
Co-authored-by: Mishig <[email protected]>
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <[email protected]>
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <[email protected]>
* Added app logo in corner of thumbnail and clamped description length
* improved thumbnails
* Remove warnings
* Reuse Assisntants settings component (#678)
* Update Assisntants settings
* format
* [Assistants] Use textToImage task for avatar generation (#662)
* Generate assistants avatar using stablediffusion
* wording
* Update +page.server.ts
Co-authored-by: Michael Fried <[email protected]>
* Add timeout & controls to avatar generation
* Add controls for avatar generation in .env
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <[email protected]>
* Update src/lib/components/AssistantSettings.svelte
Co-authored-by: Mishig <[email protected]>
* Fix avatar gen feature flag
* Can only upload avatar if generate is unchecked
---------
Co-authored-by: Michael Fried <[email protected]>
Co-authored-by: Mishig <[email protected]>
* layout
* small fixes
* hint
* Show feature if login is not required
* lint
* Only show creator name if it's defined
* tweaks
* thumbnail update
* thumbnail font-size
* Always display model at the bottom
* Bottom links now go to settings
* fix lint
* silent release
* fix bg on share link
* [Assistant] Delete avatar button instead of reset (#725)
* Add rate-limited image generating endpoint
* Add generate avatar button
* add little padding for firefox focus ring
* format
* fix upload image bug
* Fix uploads, replace reset by delete
* left-align buttons
* rm avatar generation feature
* final changes to delete feature
* sys prompt min height
* padding
* Add object-cover everywhere
---------
Co-authored-by: Victor Mustar <[email protected]>
---------
Co-authored-by: Mishig <[email protected]>
Co-authored-by: Victor Mustar <[email protected]>
Co-authored-by: Michael Fried <[email protected]>
- .env +4 -1
- .env.template +2 -1
- .vscode/settings.json +1 -1
- package-lock.json +370 -0
- package.json +3 -0
- src/lib/components/AssistantSettings.svelte +277 -0
- src/lib/components/DisclaimerModal.svelte +1 -2
- src/lib/components/LoginModal.svelte +0 -1
- src/lib/components/NavConversationItem.svelte +22 -4
- src/lib/components/NavMenu.svelte +2 -7
- src/lib/components/chat/AssistantIntroduction.svelte +85 -0
- src/lib/components/chat/ChatMessages.svelte +33 -2
- src/lib/components/chat/ChatWindow.svelte +16 -15
- src/lib/server/database.ts +8 -0
- src/lib/stores/settings.ts +4 -0
- src/lib/types/Assistant.ts +15 -0
- src/lib/types/ConvSidebar.ts +8 -0
- src/lib/types/Conversation.ts +2 -0
- src/lib/types/Report.ts +10 -0
- src/lib/types/Settings.ts +5 -0
- src/lib/types/SharedConversation.ts +2 -0
- src/lib/utils/timeout.ts +6 -0
- src/routes/+layout.server.ts +67 -29
- src/routes/+layout.svelte +19 -8
- src/routes/+page.svelte +20 -1
- src/routes/assistant/[assistantId]/+page.server.ts +20 -0
- src/routes/assistant/[assistantId]/+page.svelte +112 -0
- src/routes/assistant/[assistantId]/thumbnail.png/+server.ts +64 -0
- src/routes/assistant/[assistantId]/thumbnail.png/ChatThumbnail.svelte +41 -0
- src/routes/conversation/+server.ts +14 -3
- src/routes/conversation/[id]/+page.server.ts +10 -0
- src/routes/conversation/[id]/share/+server.ts +1 -0
- src/routes/settings/+layout.server.ts +31 -0
- src/routes/settings/+layout.svelte +65 -18
- src/routes/settings/+server.ts +1 -2
- src/routes/settings/[...model]/+page.svelte +1 -1
- src/routes/settings/assistants/[assistantId]/+page.server.ts +115 -0
- src/routes/settings/assistants/[assistantId]/+page.svelte +156 -0
- src/routes/settings/assistants/[assistantId]/+page.ts +14 -0
- src/routes/settings/assistants/[assistantId]/avatar/+server.ts +46 -0
- src/routes/settings/assistants/[assistantId]/edit/+page.server.ts +136 -0
- src/routes/settings/assistants/[assistantId]/edit/+page.svelte +12 -0
- src/routes/settings/assistants/new/+page.server.ts +112 -0
- src/routes/settings/assistants/new/+page.svelte +9 -0
- static/fonts/Inter-Black.ttf +0 -0
- static/fonts/Inter-Bold.ttf +0 -0
- static/fonts/Inter-ExtraBold.ttf +0 -0
- static/fonts/Inter-ExtraLight.ttf +0 -0
- static/fonts/Inter-Light.ttf +0 -0
- static/fonts/Inter-Medium.ttf +0 -0
@@ -112,6 +112,7 @@ PARQUET_EXPORT_SECRET=
|
|
112 |
RATE_LIMIT= # requests per minute
|
113 |
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
|
114 |
|
|
|
115 |
PUBLIC_APP_NAME=ChatUI # name used as title throughout the app
|
116 |
PUBLIC_APP_ASSETS=chatui # used to find logos & favicons in static/$PUBLIC_APP_ASSETS
|
117 |
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
|
|
126 |
# PUBLIC_APP_COLOR=yellow
|
127 |
# PUBLIC_APP_DESCRIPTION="Making the community's best AI chat models available to everyone."
|
128 |
# PUBLIC_APP_DATA_SHARING=1
|
129 |
-
# PUBLIC_APP_DISCLAIMER=1
|
|
|
|
|
|
112 |
RATE_LIMIT= # requests per minute
|
113 |
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
|
114 |
|
115 |
+
APP_BASE="" # base path of the app, e.g. /chat, left blank as default
|
116 |
PUBLIC_APP_NAME=ChatUI # name used as title throughout the app
|
117 |
PUBLIC_APP_ASSETS=chatui # used to find logos & favicons in static/$PUBLIC_APP_ASSETS
|
118 |
PUBLIC_APP_COLOR=blue # can be any of tailwind colors: https://tailwindcss.com/docs/customizing-colors#default-color-palette
|
|
|
127 |
# PUBLIC_APP_COLOR=yellow
|
128 |
# PUBLIC_APP_DESCRIPTION="Making the community's best AI chat models available to everyone."
|
129 |
# PUBLIC_APP_DATA_SHARING=1
|
130 |
+
# PUBLIC_APP_DISCLAIMER=1
|
131 |
+
|
132 |
+
ENABLE_ASSISTANTS=false #set to true to enable assistants feature
|
@@ -254,4 +254,5 @@ PUBLIC_GOOGLE_ANALYTICS_ID=G-8Q63TH4CSL
|
|
254 |
# ADDRESS_HEADER=X-Forwarded-For
|
255 |
# XFF_DEPTH=2
|
256 |
|
257 |
-
|
|
|
|
254 |
# ADDRESS_HEADER=X-Forwarded-For
|
255 |
# XFF_DEPTH=2
|
256 |
|
257 |
+
ENABLE_ASSISTANTS=true
|
258 |
+
EXPOSE_API=false
|
@@ -2,7 +2,7 @@
|
|
2 |
"editor.formatOnSave": true,
|
3 |
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
4 |
"editor.codeActionsOnSave": {
|
5 |
-
"source.fixAll":
|
6 |
},
|
7 |
"eslint.validate": ["javascript", "svelte"]
|
8 |
}
|
|
|
2 |
"editor.formatOnSave": true,
|
3 |
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
4 |
"editor.codeActionsOnSave": {
|
5 |
+
"source.fixAll": "explicit"
|
6 |
},
|
7 |
"eslint.validate": ["javascript", "svelte"]
|
8 |
}
|
@@ -11,6 +11,7 @@
|
|
11 |
"@huggingface/hub": "^0.5.1",
|
12 |
"@huggingface/inference": "^2.6.3",
|
13 |
"@iconify-json/bi": "^1.1.21",
|
|
|
14 |
"@xenova/transformers": "^2.6.0",
|
15 |
"autoprefixer": "^10.4.14",
|
16 |
"browser-image-resizer": "^2.4.1",
|
@@ -28,6 +29,8 @@
|
|
28 |
"parquetjs": "^0.11.2",
|
29 |
"postcss": "^8.4.31",
|
30 |
"saslprep": "^1.0.3",
|
|
|
|
|
31 |
"serpapi": "^1.1.1",
|
32 |
"tailwind-scrollbar": "^3.0.0",
|
33 |
"tailwindcss": "^3.4.0",
|
@@ -790,6 +793,208 @@
|
|
790 |
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
791 |
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
792 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
793 |
"node_modules/@rollup/plugin-commonjs": {
|
794 |
"version": "25.0.7",
|
795 |
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz",
|
@@ -922,6 +1127,21 @@
|
|
922 |
}
|
923 |
}
|
924 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
925 |
"node_modules/@sveltejs/adapter-node": {
|
926 |
"version": "1.3.1",
|
927 |
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.3.1.tgz",
|
@@ -1931,6 +2151,14 @@
|
|
1931 |
"node": ">= 6"
|
1932 |
}
|
1933 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1934 |
"node_modules/caniuse-lite": {
|
1935 |
"version": "1.0.30001542",
|
1936 |
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001542.tgz",
|
@@ -2190,6 +2418,34 @@
|
|
2190 |
"node": "*"
|
2191 |
}
|
2192 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2193 |
"node_modules/css-tree": {
|
2194 |
"version": "2.3.1",
|
2195 |
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
@@ -2472,6 +2728,11 @@
|
|
2472 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.359.tgz",
|
2473 |
"integrity": "sha512-OoVcngKCIuNXtZnsYoqlCvr0Cf3NIPzDIgwUfI9bdTFjXCrr79lI0kwQstLPZ7WhCezLlGksZk/BFAzoXC7GDw=="
|
2474 |
},
|
|
|
|
|
|
|
|
|
|
|
2475 |
"node_modules/end-of-stream": {
|
2476 |
"version": "1.4.4",
|
2477 |
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
@@ -2542,6 +2803,11 @@
|
|
2542 |
"node": ">=6"
|
2543 |
}
|
2544 |
},
|
|
|
|
|
|
|
|
|
|
|
2545 |
"node_modules/escape-string-regexp": {
|
2546 |
"version": "4.0.0",
|
2547 |
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
@@ -2874,6 +3140,11 @@
|
|
2874 |
"reusify": "^1.0.4"
|
2875 |
}
|
2876 |
},
|
|
|
|
|
|
|
|
|
|
|
2877 |
"node_modules/file-entry-cache": {
|
2878 |
"version": "6.0.1",
|
2879 |
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
@@ -3184,6 +3455,17 @@
|
|
3184 |
"node": ">= 0.4"
|
3185 |
}
|
3186 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3187 |
"node_modules/highlight.js": {
|
3188 |
"version": "11.7.0",
|
3189 |
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz",
|
@@ -3682,6 +3964,23 @@
|
|
3682 |
"node": ">=10"
|
3683 |
}
|
3684 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3685 |
"node_modules/lines-and-columns": {
|
3686 |
"version": "1.2.4",
|
3687 |
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
@@ -4417,6 +4716,11 @@
|
|
4417 |
"url": "https://github.com/sponsors/sindresorhus"
|
4418 |
}
|
4419 |
},
|
|
|
|
|
|
|
|
|
|
|
4420 |
"node_modules/parent-module": {
|
4421 |
"version": "1.0.1",
|
4422 |
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
@@ -4457,6 +4761,15 @@
|
|
4457 |
"node": ">=0.6.19"
|
4458 |
}
|
4459 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4460 |
"node_modules/parse5": {
|
4461 |
"version": "7.1.2",
|
4462 |
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
@@ -5290,6 +5603,34 @@
|
|
5290 |
"node": ">=6"
|
5291 |
}
|
5292 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5293 |
"node_modules/saxes": {
|
5294 |
"version": "6.0.0",
|
5295 |
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
@@ -5553,6 +5894,11 @@
|
|
5553 |
"safe-buffer": "~5.2.0"
|
5554 |
}
|
5555 |
},
|
|
|
|
|
|
|
|
|
|
|
5556 |
"node_modules/strip-ansi": {
|
5557 |
"version": "6.0.1",
|
5558 |
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
@@ -6054,6 +6400,11 @@
|
|
6054 |
"globrex": "^0.1.2"
|
6055 |
}
|
6056 |
},
|
|
|
|
|
|
|
|
|
|
|
6057 |
"node_modules/tinybench": {
|
6058 |
"version": "2.5.0",
|
6059 |
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz",
|
@@ -6270,6 +6621,11 @@
|
|
6270 |
"node": ">=0.8.0"
|
6271 |
}
|
6272 |
},
|
|
|
|
|
|
|
|
|
|
|
6273 |
"node_modules/undici": {
|
6274 |
"version": "5.26.4",
|
6275 |
"resolved": "https://registry.npmjs.org/undici/-/undici-5.26.4.tgz",
|
@@ -6281,6 +6637,15 @@
|
|
6281 |
"node": ">=14.0"
|
6282 |
}
|
6283 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6284 |
"node_modules/universalify": {
|
6285 |
"version": "0.2.0",
|
6286 |
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
@@ -6769,6 +7134,11 @@
|
|
6769 |
"url": "https://github.com/sponsors/sindresorhus"
|
6770 |
}
|
6771 |
},
|
|
|
|
|
|
|
|
|
|
|
6772 |
"node_modules/zod": {
|
6773 |
"version": "3.22.3",
|
6774 |
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
|
|
11 |
"@huggingface/hub": "^0.5.1",
|
12 |
"@huggingface/inference": "^2.6.3",
|
13 |
"@iconify-json/bi": "^1.1.21",
|
14 |
+
"@resvg/resvg-js": "^2.6.0",
|
15 |
"@xenova/transformers": "^2.6.0",
|
16 |
"autoprefixer": "^10.4.14",
|
17 |
"browser-image-resizer": "^2.4.1",
|
|
|
29 |
"parquetjs": "^0.11.2",
|
30 |
"postcss": "^8.4.31",
|
31 |
"saslprep": "^1.0.3",
|
32 |
+
"satori": "^0.10.11",
|
33 |
+
"satori-html": "^0.3.2",
|
34 |
"serpapi": "^1.1.1",
|
35 |
"tailwind-scrollbar": "^3.0.0",
|
36 |
"tailwindcss": "^3.4.0",
|
|
|
793 |
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
794 |
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
795 |
},
|
796 |
+
"node_modules/@resvg/resvg-js": {
|
797 |
+
"version": "2.6.0",
|
798 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.0.tgz",
|
799 |
+
"integrity": "sha512-Tf3YpbBKcQn991KKcw/vg7vZf98v01seSv6CVxZBbRkL/xyjnoYB6KgrFL6zskT1A4dWC/vg77KyNOW+ePaNlA==",
|
800 |
+
"engines": {
|
801 |
+
"node": ">= 10"
|
802 |
+
},
|
803 |
+
"optionalDependencies": {
|
804 |
+
"@resvg/resvg-js-android-arm-eabi": "2.6.0",
|
805 |
+
"@resvg/resvg-js-android-arm64": "2.6.0",
|
806 |
+
"@resvg/resvg-js-darwin-arm64": "2.6.0",
|
807 |
+
"@resvg/resvg-js-darwin-x64": "2.6.0",
|
808 |
+
"@resvg/resvg-js-linux-arm-gnueabihf": "2.6.0",
|
809 |
+
"@resvg/resvg-js-linux-arm64-gnu": "2.6.0",
|
810 |
+
"@resvg/resvg-js-linux-arm64-musl": "2.6.0",
|
811 |
+
"@resvg/resvg-js-linux-x64-gnu": "2.6.0",
|
812 |
+
"@resvg/resvg-js-linux-x64-musl": "2.6.0",
|
813 |
+
"@resvg/resvg-js-win32-arm64-msvc": "2.6.0",
|
814 |
+
"@resvg/resvg-js-win32-ia32-msvc": "2.6.0",
|
815 |
+
"@resvg/resvg-js-win32-x64-msvc": "2.6.0"
|
816 |
+
}
|
817 |
+
},
|
818 |
+
"node_modules/@resvg/resvg-js-android-arm-eabi": {
|
819 |
+
"version": "2.6.0",
|
820 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.0.tgz",
|
821 |
+
"integrity": "sha512-lJnZ/2P5aMocrFMW7HWhVne5gH82I8xH6zsfH75MYr4+/JOaVcGCTEQ06XFohGMdYRP3v05SSPLPvTM/RHjxfA==",
|
822 |
+
"cpu": [
|
823 |
+
"arm"
|
824 |
+
],
|
825 |
+
"optional": true,
|
826 |
+
"os": [
|
827 |
+
"android"
|
828 |
+
],
|
829 |
+
"engines": {
|
830 |
+
"node": ">= 10"
|
831 |
+
}
|
832 |
+
},
|
833 |
+
"node_modules/@resvg/resvg-js-android-arm64": {
|
834 |
+
"version": "2.6.0",
|
835 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.0.tgz",
|
836 |
+
"integrity": "sha512-N527f529bjMwYWShZYfBD60dXA4Fux+D695QsHQ93BDYZSHUoOh1CUGUyICevnTxs7VgEl98XpArmUWBZQVMfQ==",
|
837 |
+
"cpu": [
|
838 |
+
"arm64"
|
839 |
+
],
|
840 |
+
"optional": true,
|
841 |
+
"os": [
|
842 |
+
"android"
|
843 |
+
],
|
844 |
+
"engines": {
|
845 |
+
"node": ">= 10"
|
846 |
+
}
|
847 |
+
},
|
848 |
+
"node_modules/@resvg/resvg-js-darwin-arm64": {
|
849 |
+
"version": "2.6.0",
|
850 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.0.tgz",
|
851 |
+
"integrity": "sha512-MabUKLVayEwlPo0mIqAmMt+qESN8LltCvv5+GLgVga1avpUrkxj/fkU1TKm8kQegutUjbP/B0QuMuUr0uhF8ew==",
|
852 |
+
"cpu": [
|
853 |
+
"arm64"
|
854 |
+
],
|
855 |
+
"optional": true,
|
856 |
+
"os": [
|
857 |
+
"darwin"
|
858 |
+
],
|
859 |
+
"engines": {
|
860 |
+
"node": ">= 10"
|
861 |
+
}
|
862 |
+
},
|
863 |
+
"node_modules/@resvg/resvg-js-darwin-x64": {
|
864 |
+
"version": "2.6.0",
|
865 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.0.tgz",
|
866 |
+
"integrity": "sha512-zrFetdnSw/suXjmyxSjfDV7i61hahv6DDG6kM7BYN2yJ3Es5+BZtqYZTcIWogPJedYKmzN1YTMWGd/3f0ubFiA==",
|
867 |
+
"cpu": [
|
868 |
+
"x64"
|
869 |
+
],
|
870 |
+
"optional": true,
|
871 |
+
"os": [
|
872 |
+
"darwin"
|
873 |
+
],
|
874 |
+
"engines": {
|
875 |
+
"node": ">= 10"
|
876 |
+
}
|
877 |
+
},
|
878 |
+
"node_modules/@resvg/resvg-js-linux-arm-gnueabihf": {
|
879 |
+
"version": "2.6.0",
|
880 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.0.tgz",
|
881 |
+
"integrity": "sha512-sH4gxXt7v7dGwjGyzLwn7SFGvwZG6DQqLaZ11MmzbCwd9Zosy1TnmrMJfn6TJ7RHezmQMgBPi18bl55FZ1AT4A==",
|
882 |
+
"cpu": [
|
883 |
+
"arm"
|
884 |
+
],
|
885 |
+
"optional": true,
|
886 |
+
"os": [
|
887 |
+
"linux"
|
888 |
+
],
|
889 |
+
"engines": {
|
890 |
+
"node": ">= 10"
|
891 |
+
}
|
892 |
+
},
|
893 |
+
"node_modules/@resvg/resvg-js-linux-arm64-gnu": {
|
894 |
+
"version": "2.6.0",
|
895 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.0.tgz",
|
896 |
+
"integrity": "sha512-fCyMncqCJtrlANADIduYF4IfnWQ295UKib7DAxFXQhBsM9PLDTpizr0qemZcCNadcwSVHnAIzL4tliZhCM8P6A==",
|
897 |
+
"cpu": [
|
898 |
+
"arm64"
|
899 |
+
],
|
900 |
+
"optional": true,
|
901 |
+
"os": [
|
902 |
+
"linux"
|
903 |
+
],
|
904 |
+
"engines": {
|
905 |
+
"node": ">= 10"
|
906 |
+
}
|
907 |
+
},
|
908 |
+
"node_modules/@resvg/resvg-js-linux-arm64-musl": {
|
909 |
+
"version": "2.6.0",
|
910 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.0.tgz",
|
911 |
+
"integrity": "sha512-ouLjTgBQHQyxLht4FdMPTvuY8xzJigM9EM2Tlu0llWkN1mKyTQrvYWi6TA6XnKdzDJHy7ZLpWpjZi7F5+Pg+Vg==",
|
912 |
+
"cpu": [
|
913 |
+
"arm64"
|
914 |
+
],
|
915 |
+
"optional": true,
|
916 |
+
"os": [
|
917 |
+
"linux"
|
918 |
+
],
|
919 |
+
"engines": {
|
920 |
+
"node": ">= 10"
|
921 |
+
}
|
922 |
+
},
|
923 |
+
"node_modules/@resvg/resvg-js-linux-x64-gnu": {
|
924 |
+
"version": "2.6.0",
|
925 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.0.tgz",
|
926 |
+
"integrity": "sha512-n3zC8DWsvxC1AwxpKFclIPapDFibs5XdIRoV/mcIlxlh0vseW1F49b97F33BtJQRmlntsqqN6GMMqx8byB7B+Q==",
|
927 |
+
"cpu": [
|
928 |
+
"x64"
|
929 |
+
],
|
930 |
+
"optional": true,
|
931 |
+
"os": [
|
932 |
+
"linux"
|
933 |
+
],
|
934 |
+
"engines": {
|
935 |
+
"node": ">= 10"
|
936 |
+
}
|
937 |
+
},
|
938 |
+
"node_modules/@resvg/resvg-js-linux-x64-musl": {
|
939 |
+
"version": "2.6.0",
|
940 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.0.tgz",
|
941 |
+
"integrity": "sha512-n4tasK1HOlAxdTEROgYA1aCfsEKk0UOFDNd/AQTTZlTmCbHKXPq+O8npaaKlwXquxlVK8vrkcWbksbiGqbCAcw==",
|
942 |
+
"cpu": [
|
943 |
+
"x64"
|
944 |
+
],
|
945 |
+
"optional": true,
|
946 |
+
"os": [
|
947 |
+
"linux"
|
948 |
+
],
|
949 |
+
"engines": {
|
950 |
+
"node": ">= 10"
|
951 |
+
}
|
952 |
+
},
|
953 |
+
"node_modules/@resvg/resvg-js-win32-arm64-msvc": {
|
954 |
+
"version": "2.6.0",
|
955 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.0.tgz",
|
956 |
+
"integrity": "sha512-X2+EoBJFwDI5LDVb51Sk7ldnVLitMGr9WwU/i21i3fAeAXZb3hM16k67DeTy16OYkT2dk/RfU1tP1wG+rWbz2Q==",
|
957 |
+
"cpu": [
|
958 |
+
"arm64"
|
959 |
+
],
|
960 |
+
"optional": true,
|
961 |
+
"os": [
|
962 |
+
"win32"
|
963 |
+
],
|
964 |
+
"engines": {
|
965 |
+
"node": ">= 10"
|
966 |
+
}
|
967 |
+
},
|
968 |
+
"node_modules/@resvg/resvg-js-win32-ia32-msvc": {
|
969 |
+
"version": "2.6.0",
|
970 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.0.tgz",
|
971 |
+
"integrity": "sha512-L7oevWjQoUgK5W1fCKn0euSVemhDXVhrjtwqpc7MwBKKimYeiOshO1Li1pa8bBt5PESahenhWgdB6lav9O0fEg==",
|
972 |
+
"cpu": [
|
973 |
+
"ia32"
|
974 |
+
],
|
975 |
+
"optional": true,
|
976 |
+
"os": [
|
977 |
+
"win32"
|
978 |
+
],
|
979 |
+
"engines": {
|
980 |
+
"node": ">= 10"
|
981 |
+
}
|
982 |
+
},
|
983 |
+
"node_modules/@resvg/resvg-js-win32-x64-msvc": {
|
984 |
+
"version": "2.6.0",
|
985 |
+
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.0.tgz",
|
986 |
+
"integrity": "sha512-8lJlghb+Unki5AyKgsnFbRJwkEj9r1NpwyuBG8yEJiG1W9eEGl03R3I7bsVa3haof/3J1NlWf0rzSa1G++A2iw==",
|
987 |
+
"cpu": [
|
988 |
+
"x64"
|
989 |
+
],
|
990 |
+
"optional": true,
|
991 |
+
"os": [
|
992 |
+
"win32"
|
993 |
+
],
|
994 |
+
"engines": {
|
995 |
+
"node": ">= 10"
|
996 |
+
}
|
997 |
+
},
|
998 |
"node_modules/@rollup/plugin-commonjs": {
|
999 |
"version": "25.0.7",
|
1000 |
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz",
|
|
|
1127 |
}
|
1128 |
}
|
1129 |
},
|
1130 |
+
"node_modules/@shuding/opentype.js": {
|
1131 |
+
"version": "1.4.0-beta.0",
|
1132 |
+
"resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
|
1133 |
+
"integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
|
1134 |
+
"dependencies": {
|
1135 |
+
"fflate": "^0.7.3",
|
1136 |
+
"string.prototype.codepointat": "^0.2.1"
|
1137 |
+
},
|
1138 |
+
"bin": {
|
1139 |
+
"ot": "bin/ot"
|
1140 |
+
},
|
1141 |
+
"engines": {
|
1142 |
+
"node": ">= 8.0.0"
|
1143 |
+
}
|
1144 |
+
},
|
1145 |
"node_modules/@sveltejs/adapter-node": {
|
1146 |
"version": "1.3.1",
|
1147 |
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.3.1.tgz",
|
|
|
2151 |
"node": ">= 6"
|
2152 |
}
|
2153 |
},
|
2154 |
+
"node_modules/camelize": {
|
2155 |
+
"version": "1.0.1",
|
2156 |
+
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
2157 |
+
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
2158 |
+
"funding": {
|
2159 |
+
"url": "https://github.com/sponsors/ljharb"
|
2160 |
+
}
|
2161 |
+
},
|
2162 |
"node_modules/caniuse-lite": {
|
2163 |
"version": "1.0.30001542",
|
2164 |
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001542.tgz",
|
|
|
2418 |
"node": "*"
|
2419 |
}
|
2420 |
},
|
2421 |
+
"node_modules/css-background-parser": {
|
2422 |
+
"version": "0.1.0",
|
2423 |
+
"resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
|
2424 |
+
"integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="
|
2425 |
+
},
|
2426 |
+
"node_modules/css-box-shadow": {
|
2427 |
+
"version": "1.0.0-3",
|
2428 |
+
"resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
|
2429 |
+
"integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="
|
2430 |
+
},
|
2431 |
+
"node_modules/css-color-keywords": {
|
2432 |
+
"version": "1.0.0",
|
2433 |
+
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
2434 |
+
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
2435 |
+
"engines": {
|
2436 |
+
"node": ">=4"
|
2437 |
+
}
|
2438 |
+
},
|
2439 |
+
"node_modules/css-to-react-native": {
|
2440 |
+
"version": "3.2.0",
|
2441 |
+
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
2442 |
+
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
2443 |
+
"dependencies": {
|
2444 |
+
"camelize": "^1.0.0",
|
2445 |
+
"css-color-keywords": "^1.0.0",
|
2446 |
+
"postcss-value-parser": "^4.0.2"
|
2447 |
+
}
|
2448 |
+
},
|
2449 |
"node_modules/css-tree": {
|
2450 |
"version": "2.3.1",
|
2451 |
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
|
|
2728 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.359.tgz",
|
2729 |
"integrity": "sha512-OoVcngKCIuNXtZnsYoqlCvr0Cf3NIPzDIgwUfI9bdTFjXCrr79lI0kwQstLPZ7WhCezLlGksZk/BFAzoXC7GDw=="
|
2730 |
},
|
2731 |
+
"node_modules/emoji-regex": {
|
2732 |
+
"version": "10.3.0",
|
2733 |
+
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
|
2734 |
+
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
|
2735 |
+
},
|
2736 |
"node_modules/end-of-stream": {
|
2737 |
"version": "1.4.4",
|
2738 |
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
|
|
2803 |
"node": ">=6"
|
2804 |
}
|
2805 |
},
|
2806 |
+
"node_modules/escape-html": {
|
2807 |
+
"version": "1.0.3",
|
2808 |
+
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
2809 |
+
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
2810 |
+
},
|
2811 |
"node_modules/escape-string-regexp": {
|
2812 |
"version": "4.0.0",
|
2813 |
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
|
|
3140 |
"reusify": "^1.0.4"
|
3141 |
}
|
3142 |
},
|
3143 |
+
"node_modules/fflate": {
|
3144 |
+
"version": "0.7.4",
|
3145 |
+
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
|
3146 |
+
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="
|
3147 |
+
},
|
3148 |
"node_modules/file-entry-cache": {
|
3149 |
"version": "6.0.1",
|
3150 |
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
|
|
3455 |
"node": ">= 0.4"
|
3456 |
}
|
3457 |
},
|
3458 |
+
"node_modules/hex-rgb": {
|
3459 |
+
"version": "4.3.0",
|
3460 |
+
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
|
3461 |
+
"integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
|
3462 |
+
"engines": {
|
3463 |
+
"node": ">=6"
|
3464 |
+
},
|
3465 |
+
"funding": {
|
3466 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
3467 |
+
}
|
3468 |
+
},
|
3469 |
"node_modules/highlight.js": {
|
3470 |
"version": "11.7.0",
|
3471 |
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz",
|
|
|
3964 |
"node": ">=10"
|
3965 |
}
|
3966 |
},
|
3967 |
+
"node_modules/linebreak": {
|
3968 |
+
"version": "1.1.0",
|
3969 |
+
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
|
3970 |
+
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
|
3971 |
+
"dependencies": {
|
3972 |
+
"base64-js": "0.0.8",
|
3973 |
+
"unicode-trie": "^2.0.0"
|
3974 |
+
}
|
3975 |
+
},
|
3976 |
+
"node_modules/linebreak/node_modules/base64-js": {
|
3977 |
+
"version": "0.0.8",
|
3978 |
+
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
|
3979 |
+
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
|
3980 |
+
"engines": {
|
3981 |
+
"node": ">= 0.4"
|
3982 |
+
}
|
3983 |
+
},
|
3984 |
"node_modules/lines-and-columns": {
|
3985 |
"version": "1.2.4",
|
3986 |
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
|
|
4716 |
"url": "https://github.com/sponsors/sindresorhus"
|
4717 |
}
|
4718 |
},
|
4719 |
+
"node_modules/pako": {
|
4720 |
+
"version": "0.2.9",
|
4721 |
+
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
4722 |
+
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
|
4723 |
+
},
|
4724 |
"node_modules/parent-module": {
|
4725 |
"version": "1.0.1",
|
4726 |
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
|
|
4761 |
"node": ">=0.6.19"
|
4762 |
}
|
4763 |
},
|
4764 |
+
"node_modules/parse-css-color": {
|
4765 |
+
"version": "0.2.1",
|
4766 |
+
"resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
|
4767 |
+
"integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
|
4768 |
+
"dependencies": {
|
4769 |
+
"color-name": "^1.1.4",
|
4770 |
+
"hex-rgb": "^4.1.0"
|
4771 |
+
}
|
4772 |
+
},
|
4773 |
"node_modules/parse5": {
|
4774 |
"version": "7.1.2",
|
4775 |
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
|
|
5603 |
"node": ">=6"
|
5604 |
}
|
5605 |
},
|
5606 |
+
"node_modules/satori": {
|
5607 |
+
"version": "0.10.11",
|
5608 |
+
"resolved": "https://registry.npmjs.org/satori/-/satori-0.10.11.tgz",
|
5609 |
+
"integrity": "sha512-yLm1xPRPZUaKcBZJ6nmezoJjHB4MqV8x7Mu0PyZUJodRWRDD27UbeMwzuY9LEGG57WYLO4CQsGPlbHWV1Ex9TQ==",
|
5610 |
+
"dependencies": {
|
5611 |
+
"@shuding/opentype.js": "1.4.0-beta.0",
|
5612 |
+
"css-background-parser": "^0.1.0",
|
5613 |
+
"css-box-shadow": "1.0.0-3",
|
5614 |
+
"css-to-react-native": "^3.0.0",
|
5615 |
+
"emoji-regex": "^10.2.1",
|
5616 |
+
"escape-html": "^1.0.3",
|
5617 |
+
"linebreak": "^1.1.0",
|
5618 |
+
"parse-css-color": "^0.2.1",
|
5619 |
+
"postcss-value-parser": "^4.2.0",
|
5620 |
+
"yoga-wasm-web": "^0.3.3"
|
5621 |
+
},
|
5622 |
+
"engines": {
|
5623 |
+
"node": ">=16"
|
5624 |
+
}
|
5625 |
+
},
|
5626 |
+
"node_modules/satori-html": {
|
5627 |
+
"version": "0.3.2",
|
5628 |
+
"resolved": "https://registry.npmjs.org/satori-html/-/satori-html-0.3.2.tgz",
|
5629 |
+
"integrity": "sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==",
|
5630 |
+
"dependencies": {
|
5631 |
+
"ultrahtml": "^1.2.0"
|
5632 |
+
}
|
5633 |
+
},
|
5634 |
"node_modules/saxes": {
|
5635 |
"version": "6.0.0",
|
5636 |
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
|
|
5894 |
"safe-buffer": "~5.2.0"
|
5895 |
}
|
5896 |
},
|
5897 |
+
"node_modules/string.prototype.codepointat": {
|
5898 |
+
"version": "0.2.1",
|
5899 |
+
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
|
5900 |
+
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
|
5901 |
+
},
|
5902 |
"node_modules/strip-ansi": {
|
5903 |
"version": "6.0.1",
|
5904 |
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
|
|
6400 |
"globrex": "^0.1.2"
|
6401 |
}
|
6402 |
},
|
6403 |
+
"node_modules/tiny-inflate": {
|
6404 |
+
"version": "1.0.3",
|
6405 |
+
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
6406 |
+
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
|
6407 |
+
},
|
6408 |
"node_modules/tinybench": {
|
6409 |
"version": "2.5.0",
|
6410 |
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz",
|
|
|
6621 |
"node": ">=0.8.0"
|
6622 |
}
|
6623 |
},
|
6624 |
+
"node_modules/ultrahtml": {
|
6625 |
+
"version": "1.5.2",
|
6626 |
+
"resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.2.tgz",
|
6627 |
+
"integrity": "sha512-qh4mBffhlkiXwDAOxvSGxhL0QEQsTbnP9BozOK3OYPEGvPvdWzvAUaXNtUSMdNsKDtuyjEbyVUPFZ52SSLhLqw=="
|
6628 |
+
},
|
6629 |
"node_modules/undici": {
|
6630 |
"version": "5.26.4",
|
6631 |
"resolved": "https://registry.npmjs.org/undici/-/undici-5.26.4.tgz",
|
|
|
6637 |
"node": ">=14.0"
|
6638 |
}
|
6639 |
},
|
6640 |
+
"node_modules/unicode-trie": {
|
6641 |
+
"version": "2.0.0",
|
6642 |
+
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
6643 |
+
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
6644 |
+
"dependencies": {
|
6645 |
+
"pako": "^0.2.5",
|
6646 |
+
"tiny-inflate": "^1.0.0"
|
6647 |
+
}
|
6648 |
+
},
|
6649 |
"node_modules/universalify": {
|
6650 |
"version": "0.2.0",
|
6651 |
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
|
|
7134 |
"url": "https://github.com/sponsors/sindresorhus"
|
7135 |
}
|
7136 |
},
|
7137 |
+
"node_modules/yoga-wasm-web": {
|
7138 |
+
"version": "0.3.3",
|
7139 |
+
"resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz",
|
7140 |
+
"integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA=="
|
7141 |
+
},
|
7142 |
"node_modules/zod": {
|
7143 |
"version": "3.22.3",
|
7144 |
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
@@ -47,6 +47,7 @@
|
|
47 |
"@huggingface/hub": "^0.5.1",
|
48 |
"@huggingface/inference": "^2.6.3",
|
49 |
"@iconify-json/bi": "^1.1.21",
|
|
|
50 |
"@xenova/transformers": "^2.6.0",
|
51 |
"autoprefixer": "^10.4.14",
|
52 |
"browser-image-resizer": "^2.4.1",
|
@@ -64,6 +65,8 @@
|
|
64 |
"parquetjs": "^0.11.2",
|
65 |
"postcss": "^8.4.31",
|
66 |
"saslprep": "^1.0.3",
|
|
|
|
|
67 |
"serpapi": "^1.1.1",
|
68 |
"tailwind-scrollbar": "^3.0.0",
|
69 |
"tailwindcss": "^3.4.0",
|
|
|
47 |
"@huggingface/hub": "^0.5.1",
|
48 |
"@huggingface/inference": "^2.6.3",
|
49 |
"@iconify-json/bi": "^1.1.21",
|
50 |
+
"@resvg/resvg-js": "^2.6.0",
|
51 |
"@xenova/transformers": "^2.6.0",
|
52 |
"autoprefixer": "^10.4.14",
|
53 |
"browser-image-resizer": "^2.4.1",
|
|
|
65 |
"parquetjs": "^0.11.2",
|
66 |
"postcss": "^8.4.31",
|
67 |
"saslprep": "^1.0.3",
|
68 |
+
"satori": "^0.10.11",
|
69 |
+
"satori-html": "^0.3.2",
|
70 |
"serpapi": "^1.1.1",
|
71 |
"tailwind-scrollbar": "^3.0.0",
|
72 |
"tailwindcss": "^3.4.0",
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import type { readAndCompressImage } from "browser-image-resizer";
|
3 |
+
import type { Model } from "$lib/types/Model";
|
4 |
+
import type { Assistant } from "$lib/types/Assistant";
|
5 |
+
|
6 |
+
import { onMount } from "svelte";
|
7 |
+
import { applyAction, enhance } from "$app/forms";
|
8 |
+
import { base } from "$app/paths";
|
9 |
+
import CarbonPen from "~icons/carbon/pen";
|
10 |
+
import CarbonUpload from "~icons/carbon/upload";
|
11 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
12 |
+
import IconLoading from "./icons/IconLoading.svelte";
|
13 |
+
|
14 |
+
type ActionData = {
|
15 |
+
error: boolean;
|
16 |
+
errors: {
|
17 |
+
field: string | number;
|
18 |
+
message: string;
|
19 |
+
}[];
|
20 |
+
} | null;
|
21 |
+
|
22 |
+
type AssistantFront = Omit<Assistant, "_id" | "createdById"> & { _id: string };
|
23 |
+
|
24 |
+
export let form: ActionData;
|
25 |
+
export let assistant: AssistantFront | undefined = undefined;
|
26 |
+
export let models: Model[] = [];
|
27 |
+
|
28 |
+
let files: FileList | null = null;
|
29 |
+
|
30 |
+
const settings = useSettingsStore();
|
31 |
+
|
32 |
+
let compress: typeof readAndCompressImage | null = null;
|
33 |
+
|
34 |
+
onMount(async () => {
|
35 |
+
const module = await import("browser-image-resizer");
|
36 |
+
compress = module.readAndCompressImage;
|
37 |
+
});
|
38 |
+
|
39 |
+
let inputMessage1 = assistant?.exampleInputs[0] ?? "";
|
40 |
+
let inputMessage2 = assistant?.exampleInputs[1] ?? "";
|
41 |
+
let inputMessage3 = assistant?.exampleInputs[2] ?? "";
|
42 |
+
let inputMessage4 = assistant?.exampleInputs[3] ?? "";
|
43 |
+
|
44 |
+
function resetErrors() {
|
45 |
+
if (form) {
|
46 |
+
form.errors = [];
|
47 |
+
form.error = false;
|
48 |
+
}
|
49 |
+
}
|
50 |
+
|
51 |
+
function onFilesChange(e: Event) {
|
52 |
+
const inputEl = e.target as HTMLInputElement;
|
53 |
+
if (inputEl.files?.length) {
|
54 |
+
files = inputEl.files;
|
55 |
+
resetErrors();
|
56 |
+
deleteExistingAvatar = false;
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
function getError(field: string, returnForm: ActionData) {
|
61 |
+
return returnForm?.errors.find((error) => error.field === field)?.message ?? "";
|
62 |
+
}
|
63 |
+
|
64 |
+
let deleteExistingAvatar = false;
|
65 |
+
|
66 |
+
let loading = false;
|
67 |
+
</script>
|
68 |
+
|
69 |
+
<form
|
70 |
+
method="POST"
|
71 |
+
class="flex h-full flex-col"
|
72 |
+
enctype="multipart/form-data"
|
73 |
+
use:enhance={async ({ formData }) => {
|
74 |
+
loading = true;
|
75 |
+
if (files?.[0] && files[0].size > 0 && compress) {
|
76 |
+
await compress(files[0], {
|
77 |
+
maxWidth: 500,
|
78 |
+
maxHeight: 500,
|
79 |
+
quality: 1,
|
80 |
+
}).then((resizedImage) => {
|
81 |
+
formData.set("avatar", resizedImage);
|
82 |
+
});
|
83 |
+
}
|
84 |
+
|
85 |
+
if (deleteExistingAvatar === true) {
|
86 |
+
if (assistant?.avatar) {
|
87 |
+
// if there is an avatar we explicitly removei t
|
88 |
+
formData.set("avatar", "null");
|
89 |
+
} else {
|
90 |
+
// else we just remove it from the input
|
91 |
+
formData.delete("avatar");
|
92 |
+
}
|
93 |
+
}
|
94 |
+
|
95 |
+
return async ({ result }) => {
|
96 |
+
loading = false;
|
97 |
+
await applyAction(result);
|
98 |
+
};
|
99 |
+
}}
|
100 |
+
>
|
101 |
+
{#if assistant}
|
102 |
+
<h2 class="text-xl font-semibold">Edit assistant ({assistant?.name ?? ""})</h2>
|
103 |
+
<p class="mb-6 text-sm text-gray-500">
|
104 |
+
Modifying an existing assistant will propagate those changes to all users.
|
105 |
+
</p>
|
106 |
+
{:else}
|
107 |
+
<h2 class="text-xl font-semibold">Create new assistant</h2>
|
108 |
+
<p class="mb-6 text-sm text-gray-500">
|
109 |
+
Assistants are public, and can be accessed by anyone with the link.
|
110 |
+
</p>
|
111 |
+
{/if}
|
112 |
+
|
113 |
+
<div class="mx-1 grid flex-1 grid-cols-2 gap-4 max-sm:grid-cols-1">
|
114 |
+
<div class="flex flex-col gap-4">
|
115 |
+
<div>
|
116 |
+
<span class="mb-1 block pb-2 text-sm font-semibold">Avatar</span>
|
117 |
+
<input
|
118 |
+
type="file"
|
119 |
+
accept="image/*"
|
120 |
+
name="avatar"
|
121 |
+
id="avatar"
|
122 |
+
class="hidden"
|
123 |
+
on:change={onFilesChange}
|
124 |
+
/>
|
125 |
+
|
126 |
+
{#if (files && files[0]) || (assistant?.avatar && !deleteExistingAvatar)}
|
127 |
+
<div class="group relative mx-auto h-12 w-12">
|
128 |
+
{#if files && files[0]}
|
129 |
+
<img
|
130 |
+
src={URL.createObjectURL(files[0])}
|
131 |
+
alt="avatar"
|
132 |
+
class="crop mx-auto h-12 w-12 cursor-pointer rounded-full object-cover"
|
133 |
+
/>
|
134 |
+
{:else if assistant?.avatar}
|
135 |
+
<img
|
136 |
+
src="{base}/settings/assistants/{assistant._id}/avatar?hash={assistant.avatar}"
|
137 |
+
alt="avatar"
|
138 |
+
class="crop mx-auto h-12 w-12 cursor-pointer rounded-full object-cover"
|
139 |
+
/>
|
140 |
+
{/if}
|
141 |
+
|
142 |
+
<label
|
143 |
+
for="avatar"
|
144 |
+
class="invisible absolute bottom-0 h-12 w-12 rounded-full bg-black bg-opacity-50 p-1 group-hover:visible hover:visible"
|
145 |
+
>
|
146 |
+
<CarbonPen class="mx-auto my-auto h-full cursor-pointer text-center text-white" />
|
147 |
+
</label>
|
148 |
+
</div>
|
149 |
+
<div class="mx-auto w-max pt-1">
|
150 |
+
<button
|
151 |
+
type="button"
|
152 |
+
on:click|stopPropagation|preventDefault={() => {
|
153 |
+
files = null;
|
154 |
+
deleteExistingAvatar = true;
|
155 |
+
}}
|
156 |
+
class="mx-auto w-max text-center text-xs text-gray-600 hover:underline"
|
157 |
+
>
|
158 |
+
Delete
|
159 |
+
</button>
|
160 |
+
</div>
|
161 |
+
{:else}
|
162 |
+
<div class="mb-1 flex w-max flex-row gap-4">
|
163 |
+
<label
|
164 |
+
for="avatar"
|
165 |
+
class="btn flex h-8 rounded-lg border bg-white px-3 py-1 text-gray-500 shadow-sm transition-all hover:bg-gray-100"
|
166 |
+
>
|
167 |
+
<CarbonUpload class="mr-2 text-xs " /> Upload
|
168 |
+
</label>
|
169 |
+
</div>
|
170 |
+
<p class="text-xs text-red-500">{getError("avatar", form)}</p>
|
171 |
+
{/if}
|
172 |
+
</div>
|
173 |
+
|
174 |
+
<label>
|
175 |
+
<span class="mb-1 text-sm font-semibold">Name</span>
|
176 |
+
<input
|
177 |
+
name="name"
|
178 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
179 |
+
placeholder="My awesome model"
|
180 |
+
value={assistant?.name ?? ""}
|
181 |
+
/>
|
182 |
+
<p class="text-xs text-red-500">{getError("name", form)}</p>
|
183 |
+
</label>
|
184 |
+
|
185 |
+
<label>
|
186 |
+
<span class="mb-1 text-sm font-semibold">Description</span>
|
187 |
+
<textarea
|
188 |
+
name="description"
|
189 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
190 |
+
placeholder="He knows everything about python"
|
191 |
+
value={assistant?.description ?? ""}
|
192 |
+
/>
|
193 |
+
<p class="text-xs text-red-500">{getError("description", form)}</p>
|
194 |
+
</label>
|
195 |
+
|
196 |
+
<label>
|
197 |
+
<span class="mb-1 text-sm font-semibold">Model</span>
|
198 |
+
<select name="modelId" class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2">
|
199 |
+
{#each models as model}
|
200 |
+
<option
|
201 |
+
value={model.id}
|
202 |
+
selected={assistant
|
203 |
+
? assistant?.modelId === model.id
|
204 |
+
: $settings.activeModel === model.id}>{model.displayName}</option
|
205 |
+
>
|
206 |
+
{/each}
|
207 |
+
<p class="text-xs text-red-500">{getError("modelId", form)}</p>
|
208 |
+
</select>
|
209 |
+
</label>
|
210 |
+
|
211 |
+
<label>
|
212 |
+
<span class="mb-1 text-sm font-semibold">Start messages</span>
|
213 |
+
<div class="flex flex-col gap-2 md:max-h-32">
|
214 |
+
<input
|
215 |
+
name="exampleInput1"
|
216 |
+
bind:value={inputMessage1}
|
217 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
218 |
+
/>
|
219 |
+
{#if !!inputMessage1 || !!inputMessage2}
|
220 |
+
<input
|
221 |
+
name="exampleInput2"
|
222 |
+
bind:value={inputMessage2}
|
223 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
224 |
+
/>
|
225 |
+
{/if}
|
226 |
+
{#if !!inputMessage2 || !!inputMessage3}
|
227 |
+
<input
|
228 |
+
name="exampleInput3"
|
229 |
+
bind:value={inputMessage3}
|
230 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
231 |
+
/>
|
232 |
+
{/if}
|
233 |
+
{#if !!inputMessage3 || !!inputMessage4}
|
234 |
+
<input
|
235 |
+
name="exampleInput4"
|
236 |
+
bind:value={inputMessage4}
|
237 |
+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
238 |
+
/>
|
239 |
+
{/if}
|
240 |
+
</div>
|
241 |
+
<p class="text-xs text-red-500">{getError("inputMessage1", form)}</p>
|
242 |
+
</label>
|
243 |
+
</div>
|
244 |
+
|
245 |
+
<label class="flex flex-col">
|
246 |
+
<span class="mb-1 text-sm font-semibold"> Instructions (system prompt) </span>
|
247 |
+
<textarea
|
248 |
+
name="preprompt"
|
249 |
+
class="min-h-[8lh] flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
|
250 |
+
placeholder="You'll act as..."
|
251 |
+
value={assistant?.preprompt ?? ""}
|
252 |
+
/>
|
253 |
+
<p class="text-xs text-red-500">{getError("preprompt", form)}</p>
|
254 |
+
</label>
|
255 |
+
</div>
|
256 |
+
|
257 |
+
<div class="mt-5 flex justify-end gap-2">
|
258 |
+
<a
|
259 |
+
href={assistant ? `${base}/settings/assistants/${assistant?._id}` : `${base}/settings`}
|
260 |
+
class="rounded-full bg-gray-200 px-8 py-2 font-semibold text-gray-600">Cancel</a
|
261 |
+
>
|
262 |
+
<button
|
263 |
+
type="submit"
|
264 |
+
disabled={loading}
|
265 |
+
aria-disabled={loading}
|
266 |
+
class="rounded-full bg-black px-8 py-2 font-semibold md:px-20"
|
267 |
+
class:bg-gray-200={loading}
|
268 |
+
class:text-gray-600={loading}
|
269 |
+
class:text-white={!loading}
|
270 |
+
>
|
271 |
+
{assistant ? "Save" : "Create"}
|
272 |
+
{#if loading}
|
273 |
+
<IconLoading classNames="ml-2 h-min" />
|
274 |
+
{/if}
|
275 |
+
</button>
|
276 |
+
</div>
|
277 |
+
</form>
|
@@ -36,9 +36,8 @@
|
|
36 |
class:bg-white={$page.data.loginEnabled}
|
37 |
class:text-gray-800={$page.data.loginEnabled}
|
38 |
class:hover:bg-slate-100={$page.data.loginEnabled}
|
39 |
-
on:click={(
|
40 |
if (!cookiesAreEnabled()) {
|
41 |
-
e.preventDefault();
|
42 |
window.open(window.location.href, "_blank");
|
43 |
}
|
44 |
|
|
|
36 |
class:bg-white={$page.data.loginEnabled}
|
37 |
class:text-gray-800={$page.data.loginEnabled}
|
38 |
class:hover:bg-slate-100={$page.data.loginEnabled}
|
39 |
+
on:click|preventDefault|stopPropagation={() => {
|
40 |
if (!cookiesAreEnabled()) {
|
|
|
41 |
window.open(window.location.href, "_blank");
|
42 |
}
|
43 |
|
@@ -51,7 +51,6 @@
|
|
51 |
e.preventDefault();
|
52 |
window.open(window.location.href, "_blank");
|
53 |
}
|
54 |
-
|
55 |
$settings.ethicsModalAccepted = true;
|
56 |
}}
|
57 |
>
|
|
|
51 |
e.preventDefault();
|
52 |
window.open(window.location.href, "_blank");
|
53 |
}
|
|
|
54 |
$settings.ethicsModalAccepted = true;
|
55 |
}}
|
56 |
>
|
@@ -7,8 +7,10 @@
|
|
7 |
import CarbonTrashCan from "~icons/carbon/trash-can";
|
8 |
import CarbonClose from "~icons/carbon/close";
|
9 |
import CarbonEdit from "~icons/carbon/edit";
|
|
|
|
|
10 |
|
11 |
-
export let conv:
|
12 |
|
13 |
let confirmDelete = false;
|
14 |
|
@@ -16,6 +18,8 @@
|
|
16 |
deleteConversation: string;
|
17 |
editConversationTitle: { id: string; title: string };
|
18 |
}>();
|
|
|
|
|
19 |
</script>
|
20 |
|
21 |
<a
|
@@ -29,11 +33,25 @@
|
|
29 |
? 'bg-gray-100 dark:bg-gray-700'
|
30 |
: ''}"
|
31 |
>
|
32 |
-
<div class="flex-1 truncate">
|
33 |
{#if confirmDelete}
|
34 |
-
<span class="font-semibold"> Delete </span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
{/if}
|
36 |
-
{conv.title}
|
37 |
</div>
|
38 |
|
39 |
{#if confirmDelete}
|
|
|
7 |
import CarbonTrashCan from "~icons/carbon/trash-can";
|
8 |
import CarbonClose from "~icons/carbon/close";
|
9 |
import CarbonEdit from "~icons/carbon/edit";
|
10 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
11 |
+
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
12 |
|
13 |
+
export let conv: ConvSidebar;
|
14 |
|
15 |
let confirmDelete = false;
|
16 |
|
|
|
18 |
deleteConversation: string;
|
19 |
editConversationTitle: { id: string; title: string };
|
20 |
}>();
|
21 |
+
|
22 |
+
const settings = useSettingsStore();
|
23 |
</script>
|
24 |
|
25 |
<a
|
|
|
33 |
? 'bg-gray-100 dark:bg-gray-700'
|
34 |
: ''}"
|
35 |
>
|
36 |
+
<div class="flex flex-1 items-center truncate">
|
37 |
{#if confirmDelete}
|
38 |
+
<span class="mr-1 font-semibold"> Delete </span>
|
39 |
+
{/if}
|
40 |
+
{#if conv.avatarHash && !$settings.hideEmojiOnSidebar}
|
41 |
+
<img
|
42 |
+
src="{base}/settings/assistants/{conv.assistantId}/avatar?hash={conv.avatarHash}"
|
43 |
+
alt="Assistant avatar"
|
44 |
+
class="mr-1.5 inline size-4 rounded-full object-cover"
|
45 |
+
/>
|
46 |
+
{conv.title.replace(/\p{Emoji}/gu, "")}
|
47 |
+
{:else if conv.assistantId}
|
48 |
+
<div
|
49 |
+
class="mr-1.5 flex size-4 items-center justify-center rounded-full bg-gray-300 text-xs font-bold uppercase text-gray-500"
|
50 |
+
/>
|
51 |
+
{conv.title.replace(/\p{Emoji}/gu, "")}
|
52 |
+
{:else}
|
53 |
+
{conv.title}
|
54 |
{/if}
|
|
|
55 |
</div>
|
56 |
|
57 |
{#if confirmDelete}
|
@@ -7,14 +7,9 @@
|
|
7 |
import { PUBLIC_APP_NAME, PUBLIC_ORIGIN } from "$env/static/public";
|
8 |
import NavConversationItem from "./NavConversationItem.svelte";
|
9 |
import type { LayoutData } from "../../routes/$types";
|
|
|
10 |
|
11 |
-
|
12 |
-
id: string;
|
13 |
-
title: string;
|
14 |
-
updatedAt: Date;
|
15 |
-
}
|
16 |
-
|
17 |
-
export let conversations: Array<Conv> = [];
|
18 |
export let canLogin: boolean;
|
19 |
export let user: LayoutData["user"];
|
20 |
|
|
|
7 |
import { PUBLIC_APP_NAME, PUBLIC_ORIGIN } from "$env/static/public";
|
8 |
import NavConversationItem from "./NavConversationItem.svelte";
|
9 |
import type { LayoutData } from "../../routes/$types";
|
10 |
+
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
11 |
|
12 |
+
export let conversations: ConvSidebar[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
export let canLogin: boolean;
|
14 |
export let user: LayoutData["user"];
|
15 |
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { createEventDispatcher } from "svelte";
|
3 |
+
import IconGear from "~icons/bi/gear-fill";
|
4 |
+
import { base } from "$app/paths";
|
5 |
+
import type { Assistant } from "$lib/types/Assistant";
|
6 |
+
|
7 |
+
export let assistant: Pick<
|
8 |
+
Assistant,
|
9 |
+
"avatar" | "name" | "modelId" | "createdByName" | "exampleInputs" | "_id" | "description"
|
10 |
+
>;
|
11 |
+
|
12 |
+
const dispatch = createEventDispatcher<{ message: string }>();
|
13 |
+
</script>
|
14 |
+
|
15 |
+
<div class="flex h-full w-full flex-col content-center items-center justify-center">
|
16 |
+
<div
|
17 |
+
class="relative mt-auto rounded-2xl bg-gray-100 text-gray-600 dark:border-gray-800 dark:bg-gray-800/60 dark:text-gray-300"
|
18 |
+
>
|
19 |
+
<div class="flex items-center gap-4 p-4 pr-10 md:p-8 md:pt-10">
|
20 |
+
{#if assistant.avatar}
|
21 |
+
<img
|
22 |
+
src={`${base}/settings/assistants/${assistant._id.toString()}/avatar?hash=${
|
23 |
+
assistant.avatar
|
24 |
+
}`}
|
25 |
+
alt="avatar"
|
26 |
+
class="size-16 rounded-full object-cover md:size-32"
|
27 |
+
/>
|
28 |
+
{:else}
|
29 |
+
<div
|
30 |
+
class="flex size-12 flex-none items-center justify-center rounded-full bg-gray-300 object-cover text-xl font-bold uppercase text-gray-500 sm:text-4xl md:h-32 md:w-32 dark:bg-gray-600"
|
31 |
+
>
|
32 |
+
{assistant?.name[0]}
|
33 |
+
</div>
|
34 |
+
{/if}
|
35 |
+
|
36 |
+
<div class="flex h-full flex-col">
|
37 |
+
<p
|
38 |
+
class="mb-2 w-fit truncate text-ellipsis rounded-full bg-gray-200 px-3 py-1 text-xs text-gray-600 dark:bg-gray-700 dark:text-gray-400"
|
39 |
+
>
|
40 |
+
Assistant
|
41 |
+
</p>
|
42 |
+
<p class="text-xl font-bold sm:text-2xl">{assistant.name}</p>
|
43 |
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
44 |
+
{assistant.description}
|
45 |
+
</p>
|
46 |
+
|
47 |
+
{#if assistant.createdByName}
|
48 |
+
<p class="pt-2 text-sm text-gray-400 dark:text-gray-500">
|
49 |
+
Created by <a
|
50 |
+
class="hover:underline"
|
51 |
+
href="https://hf.co/{assistant.createdByName}"
|
52 |
+
target="_blank"
|
53 |
+
>
|
54 |
+
{assistant.createdByName}
|
55 |
+
</a>
|
56 |
+
</p>
|
57 |
+
{/if}
|
58 |
+
</div>
|
59 |
+
</div>
|
60 |
+
<div class="absolute right-2 top-3 sm:top-2">
|
61 |
+
<a
|
62 |
+
href="{base}/settings/assistants/{assistant._id.toString()}"
|
63 |
+
class="flex size-7 items-center justify-center rounded-full border bg-gray-200 p-1 text-xs hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
64 |
+
><IconGear /></a
|
65 |
+
>
|
66 |
+
</div>
|
67 |
+
</div>
|
68 |
+
{#if assistant.exampleInputs}
|
69 |
+
<div class="mx-auto mt-auto w-full gap-8 sm:-mb-8">
|
70 |
+
<div class="md:col-span-2 md:mt-6">
|
71 |
+
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
72 |
+
{#each assistant.exampleInputs as example}
|
73 |
+
<button
|
74 |
+
type="button"
|
75 |
+
class="truncate whitespace-nowrap rounded-xl border bg-gray-50 px-3 py-2 text-left text-smd text-gray-600 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
76 |
+
on:click={() => dispatch("message", example)}
|
77 |
+
>
|
78 |
+
{example}
|
79 |
+
</button>
|
80 |
+
{/each}
|
81 |
+
</div>
|
82 |
+
</div>
|
83 |
+
</div>
|
84 |
+
{/if}
|
85 |
+
</div>
|
@@ -10,12 +10,17 @@
|
|
10 |
import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
|
11 |
import { browser } from "$app/environment";
|
12 |
import SystemPromptModal from "../SystemPromptModal.svelte";
|
|
|
|
|
|
|
|
|
13 |
|
14 |
export let messages: Message[];
|
15 |
export let loading: boolean;
|
16 |
export let pending: boolean;
|
17 |
export let isAuthor: boolean;
|
18 |
export let currentModel: Model;
|
|
|
19 |
export let models: Model[];
|
20 |
export let preprompt: string | undefined;
|
21 |
export let readOnly: boolean;
|
@@ -42,7 +47,29 @@
|
|
42 |
>
|
43 |
<div class="mx-auto flex h-full max-w-3xl flex-col gap-6 px-5 pt-6 sm:gap-8 xl:max-w-4xl">
|
44 |
{#each messages as message, i}
|
45 |
-
{#if i === 0 &&
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
<SystemPromptModal preprompt={preprompt ?? ""} />
|
47 |
{/if}
|
48 |
<ChatMessage
|
@@ -57,7 +84,11 @@
|
|
57 |
on:continue
|
58 |
/>
|
59 |
{:else}
|
60 |
-
|
|
|
|
|
|
|
|
|
61 |
{/each}
|
62 |
{#if pending && messages[messages.length - 1]?.from === "user"}
|
63 |
<ChatMessage
|
|
|
10 |
import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
|
11 |
import { browser } from "$app/environment";
|
12 |
import SystemPromptModal from "../SystemPromptModal.svelte";
|
13 |
+
import type { Assistant } from "$lib/types/Assistant";
|
14 |
+
import AssistantIntroduction from "./AssistantIntroduction.svelte";
|
15 |
+
import { page } from "$app/stores";
|
16 |
+
import { base } from "$app/paths";
|
17 |
|
18 |
export let messages: Message[];
|
19 |
export let loading: boolean;
|
20 |
export let pending: boolean;
|
21 |
export let isAuthor: boolean;
|
22 |
export let currentModel: Model;
|
23 |
+
export let assistant: Assistant | undefined;
|
24 |
export let models: Model[];
|
25 |
export let preprompt: string | undefined;
|
26 |
export let readOnly: boolean;
|
|
|
47 |
>
|
48 |
<div class="mx-auto flex h-full max-w-3xl flex-col gap-6 px-5 pt-6 sm:gap-8 xl:max-w-4xl">
|
49 |
{#each messages as message, i}
|
50 |
+
{#if i === 0 && $page.data?.assistant}
|
51 |
+
<a
|
52 |
+
class="mx-auto flex items-center gap-1.5 rounded-full border border-gray-100 bg-gray-50 py-1 pl-1 pr-3 text-sm text-gray-800 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
|
53 |
+
href="{base}/settings/assistants/{$page.data.assistant._id}"
|
54 |
+
>
|
55 |
+
{#if $page.data?.assistant.avatar}
|
56 |
+
<img
|
57 |
+
src="{base}/settings/assistants/{$page.data?.assistant._id.toString()}/avatar?hash=${$page
|
58 |
+
.data?.assistant.avatar}"
|
59 |
+
alt="Avatar"
|
60 |
+
class="size-5 rounded-full object-cover"
|
61 |
+
/>
|
62 |
+
{:else}
|
63 |
+
<div
|
64 |
+
class="flex size-6 items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500"
|
65 |
+
>
|
66 |
+
{$page.data?.assistant.name[0]}
|
67 |
+
</div>
|
68 |
+
{/if}
|
69 |
+
|
70 |
+
{$page.data.assistant.name}
|
71 |
+
</a>
|
72 |
+
{:else if i === 0 && preprompt && preprompt != currentModel.preprompt}
|
73 |
<SystemPromptModal preprompt={preprompt ?? ""} />
|
74 |
{/if}
|
75 |
<ChatMessage
|
|
|
84 |
on:continue
|
85 |
/>
|
86 |
{:else}
|
87 |
+
{#if !assistant}
|
88 |
+
<ChatIntroduction {models} {currentModel} on:message />
|
89 |
+
{:else}
|
90 |
+
<AssistantIntroduction {assistant} on:message />
|
91 |
+
{/if}
|
92 |
{/each}
|
93 |
{#if pending && messages[messages.length - 1]?.from === "user"}
|
94 |
<ChatMessage
|
@@ -18,12 +18,12 @@
|
|
18 |
import LoginModal from "../LoginModal.svelte";
|
19 |
import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
|
20 |
import { page } from "$app/stores";
|
21 |
-
import DisclaimerModal from "../DisclaimerModal.svelte";
|
22 |
import FileDropzone from "./FileDropzone.svelte";
|
23 |
import RetryBtn from "../RetryBtn.svelte";
|
24 |
import UploadBtn from "../UploadBtn.svelte";
|
25 |
import file2base64 from "$lib/utils/file2base64";
|
26 |
-
import {
|
|
|
27 |
import ContinueBtn from "../ContinueBtn.svelte";
|
28 |
|
29 |
export let messages: Message[] = [];
|
@@ -32,6 +32,7 @@
|
|
32 |
export let shared = false;
|
33 |
export let currentModel: Model;
|
34 |
export let models: Model[];
|
|
|
35 |
export let webSearchMessages: WebSearchUpdate[] = [];
|
36 |
export let preprompt: string | undefined = undefined;
|
37 |
export let files: File[] = [];
|
@@ -78,8 +79,6 @@
|
|
78 |
|
79 |
$: sources = files.map((file) => file2base64(file));
|
80 |
|
81 |
-
const settings = useSettingsStore();
|
82 |
-
|
83 |
function onShare() {
|
84 |
dispatch("share");
|
85 |
isSharedRecently = true;
|
@@ -99,9 +98,7 @@
|
|
99 |
</script>
|
100 |
|
101 |
<div class="relative min-h-0 min-w-0">
|
102 |
-
{#if
|
103 |
-
<DisclaimerModal />
|
104 |
-
{:else if loginModalOpen}
|
105 |
<LoginModal
|
106 |
on:close={() => {
|
107 |
loginModalOpen = false;
|
@@ -113,6 +110,7 @@
|
|
113 |
{pending}
|
114 |
{currentModel}
|
115 |
{models}
|
|
|
116 |
{messages}
|
117 |
readOnly={isReadOnly}
|
118 |
isAuthor={!shared}
|
@@ -162,7 +160,7 @@
|
|
162 |
|
163 |
<div class="w-full">
|
164 |
<div class="flex w-full pb-3">
|
165 |
-
{#if $page.data.settings?.searchEnabled}
|
166 |
<WebSearchToggle />
|
167 |
{/if}
|
168 |
{#if loading}
|
@@ -252,13 +250,16 @@
|
|
252 |
class="mt-2 flex justify-between self-stretch px-1 text-xs text-gray-400/90 max-md:mb-2 max-sm:gap-2"
|
253 |
>
|
254 |
<p>
|
255 |
-
Model:
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
|
|
|
|
|
|
262 |
</p>
|
263 |
{#if messages.length}
|
264 |
<button
|
|
|
18 |
import LoginModal from "../LoginModal.svelte";
|
19 |
import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
|
20 |
import { page } from "$app/stores";
|
|
|
21 |
import FileDropzone from "./FileDropzone.svelte";
|
22 |
import RetryBtn from "../RetryBtn.svelte";
|
23 |
import UploadBtn from "../UploadBtn.svelte";
|
24 |
import file2base64 from "$lib/utils/file2base64";
|
25 |
+
import type { Assistant } from "$lib/types/Assistant";
|
26 |
+
import { base } from "$app/paths";
|
27 |
import ContinueBtn from "../ContinueBtn.svelte";
|
28 |
|
29 |
export let messages: Message[] = [];
|
|
|
32 |
export let shared = false;
|
33 |
export let currentModel: Model;
|
34 |
export let models: Model[];
|
35 |
+
export let assistant: Assistant | undefined = undefined;
|
36 |
export let webSearchMessages: WebSearchUpdate[] = [];
|
37 |
export let preprompt: string | undefined = undefined;
|
38 |
export let files: File[] = [];
|
|
|
79 |
|
80 |
$: sources = files.map((file) => file2base64(file));
|
81 |
|
|
|
|
|
82 |
function onShare() {
|
83 |
dispatch("share");
|
84 |
isSharedRecently = true;
|
|
|
98 |
</script>
|
99 |
|
100 |
<div class="relative min-h-0 min-w-0">
|
101 |
+
{#if loginModalOpen}
|
|
|
|
|
102 |
<LoginModal
|
103 |
on:close={() => {
|
104 |
loginModalOpen = false;
|
|
|
110 |
{pending}
|
111 |
{currentModel}
|
112 |
{models}
|
113 |
+
{assistant}
|
114 |
{messages}
|
115 |
readOnly={isReadOnly}
|
116 |
isAuthor={!shared}
|
|
|
160 |
|
161 |
<div class="w-full">
|
162 |
<div class="flex w-full pb-3">
|
163 |
+
{#if $page.data.settings?.searchEnabled && !assistant}
|
164 |
<WebSearchToggle />
|
165 |
{/if}
|
166 |
{#if loading}
|
|
|
250 |
class="mt-2 flex justify-between self-stretch px-1 text-xs text-gray-400/90 max-md:mb-2 max-sm:gap-2"
|
251 |
>
|
252 |
<p>
|
253 |
+
Model:
|
254 |
+
{#if !assistant}
|
255 |
+
<a href="{base}/settings/{currentModel.id}" class="hover:underline"
|
256 |
+
>{currentModel.displayName}</a
|
257 |
+
>{:else}
|
258 |
+
{@const model = models.find((m) => m.id === assistant?.modelId)}
|
259 |
+
<a href="{base}/settings/assistants/{assistant._id}" class="hover:underline"
|
260 |
+
>{model?.displayName}</a
|
261 |
+
>{/if} <span class="max-sm:hidden">·</span><br class="sm:hidden" /> Generated content may
|
262 |
+
be inaccurate or false.
|
263 |
</p>
|
264 |
{#if messages.length}
|
265 |
<button
|
@@ -7,6 +7,8 @@ import type { Settings } from "$lib/types/Settings";
|
|
7 |
import type { User } from "$lib/types/User";
|
8 |
import type { MessageEvent } from "$lib/types/MessageEvent";
|
9 |
import type { Session } from "$lib/types/Session";
|
|
|
|
|
10 |
|
11 |
if (!MONGODB_URL) {
|
12 |
throw new Error(
|
@@ -23,6 +25,8 @@ export const connectPromise = client.connect().catch(console.error);
|
|
23 |
const db = client.db(MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : ""));
|
24 |
|
25 |
const conversations = db.collection<Conversation>("conversations");
|
|
|
|
|
26 |
const sharedConversations = db.collection<SharedConversation>("sharedConversations");
|
27 |
const abortedGenerations = db.collection<AbortedGeneration>("abortedGenerations");
|
28 |
const settings = db.collection<Settings>("settings");
|
@@ -34,6 +38,8 @@ const bucket = new GridFSBucket(db, { bucketName: "files" });
|
|
34 |
export { client, db };
|
35 |
export const collections = {
|
36 |
conversations,
|
|
|
|
|
37 |
sharedConversations,
|
38 |
abortedGenerations,
|
39 |
settings,
|
@@ -66,4 +72,6 @@ client.on("open", () => {
|
|
66 |
messageEvents.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 }).catch(console.error);
|
67 |
sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(console.error);
|
68 |
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(console.error);
|
|
|
|
|
69 |
});
|
|
|
7 |
import type { User } from "$lib/types/User";
|
8 |
import type { MessageEvent } from "$lib/types/MessageEvent";
|
9 |
import type { Session } from "$lib/types/Session";
|
10 |
+
import type { Assistant } from "$lib/types/Assistant";
|
11 |
+
import type { Report } from "$lib/types/Report";
|
12 |
|
13 |
if (!MONGODB_URL) {
|
14 |
throw new Error(
|
|
|
25 |
const db = client.db(MONGODB_DB_NAME + (import.meta.env.MODE === "test" ? "-test" : ""));
|
26 |
|
27 |
const conversations = db.collection<Conversation>("conversations");
|
28 |
+
const assistants = db.collection<Assistant>("assistants");
|
29 |
+
const reports = db.collection<Report>("reports");
|
30 |
const sharedConversations = db.collection<SharedConversation>("sharedConversations");
|
31 |
const abortedGenerations = db.collection<AbortedGeneration>("abortedGenerations");
|
32 |
const settings = db.collection<Settings>("settings");
|
|
|
38 |
export { client, db };
|
39 |
export const collections = {
|
40 |
conversations,
|
41 |
+
assistants,
|
42 |
+
reports,
|
43 |
sharedConversations,
|
44 |
abortedGenerations,
|
45 |
settings,
|
|
|
72 |
messageEvents.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 }).catch(console.error);
|
73 |
sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(console.error);
|
74 |
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(console.error);
|
75 |
+
assistants.createIndex({ createdBy: 1 }).catch(console.error);
|
76 |
+
reports.createIndex({ assistantId: 1 }).catch(console.error);
|
77 |
});
|
@@ -2,6 +2,7 @@ import { browser } from "$app/environment";
|
|
2 |
import { invalidate } from "$app/navigation";
|
3 |
import { base } from "$app/paths";
|
4 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
|
|
5 |
import { getContext, setContext } from "svelte";
|
6 |
import { type Writable, writable, get } from "svelte/store";
|
7 |
|
@@ -13,7 +14,9 @@ type SettingsStore = {
|
|
13 |
activeModel: string;
|
14 |
customPrompts: Record<string, string>;
|
15 |
recentlySaved: boolean;
|
|
|
16 |
};
|
|
|
17 |
export function useSettingsStore() {
|
18 |
return getContext<Writable<SettingsStore>>("settings");
|
19 |
}
|
@@ -44,6 +47,7 @@ export function createSettingsStore(initialValue: Omit<SettingsStore, "recentlyS
|
|
44 |
}),
|
45 |
});
|
46 |
|
|
|
47 |
// set savedRecently to true for 3s
|
48 |
baseStore.update((s) => ({
|
49 |
...s,
|
|
|
2 |
import { invalidate } from "$app/navigation";
|
3 |
import { base } from "$app/paths";
|
4 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
5 |
+
import type { ObjectId } from "mongodb";
|
6 |
import { getContext, setContext } from "svelte";
|
7 |
import { type Writable, writable, get } from "svelte/store";
|
8 |
|
|
|
14 |
activeModel: string;
|
15 |
customPrompts: Record<string, string>;
|
16 |
recentlySaved: boolean;
|
17 |
+
assistants: Array<ObjectId | string>;
|
18 |
};
|
19 |
+
|
20 |
export function useSettingsStore() {
|
21 |
return getContext<Writable<SettingsStore>>("settings");
|
22 |
}
|
|
|
47 |
}),
|
48 |
});
|
49 |
|
50 |
+
invalidate(UrlDependency.ConversationList);
|
51 |
// set savedRecently to true for 3s
|
52 |
baseStore.update((s) => ({
|
53 |
...s,
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { ObjectId } from "mongodb";
|
2 |
+
import type { User } from "./User";
|
3 |
+
import type { Timestamps } from "./Timestamps";
|
4 |
+
|
5 |
+
export interface Assistant extends Timestamps {
|
6 |
+
_id: ObjectId;
|
7 |
+
createdById: User["_id"] | string; // user id or session
|
8 |
+
createdByName?: User["username"];
|
9 |
+
avatar?: string;
|
10 |
+
name: string;
|
11 |
+
description?: string;
|
12 |
+
modelId: string;
|
13 |
+
exampleInputs: string[];
|
14 |
+
preprompt: string;
|
15 |
+
}
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface ConvSidebar {
|
2 |
+
id: string;
|
3 |
+
title: string;
|
4 |
+
updatedAt: Date;
|
5 |
+
model?: string;
|
6 |
+
assistantId?: string;
|
7 |
+
avatarHash?: string;
|
8 |
+
}
|
@@ -2,6 +2,7 @@ import type { ObjectId } from "mongodb";
|
|
2 |
import type { Message } from "./Message";
|
3 |
import type { Timestamps } from "./Timestamps";
|
4 |
import type { User } from "./User";
|
|
|
5 |
|
6 |
export interface Conversation extends Timestamps {
|
7 |
_id: ObjectId;
|
@@ -20,4 +21,5 @@ export interface Conversation extends Timestamps {
|
|
20 |
};
|
21 |
|
22 |
preprompt?: string;
|
|
|
23 |
}
|
|
|
2 |
import type { Message } from "./Message";
|
3 |
import type { Timestamps } from "./Timestamps";
|
4 |
import type { User } from "./User";
|
5 |
+
import type { Assistant } from "./Assistant";
|
6 |
|
7 |
export interface Conversation extends Timestamps {
|
8 |
_id: ObjectId;
|
|
|
21 |
};
|
22 |
|
23 |
preprompt?: string;
|
24 |
+
assistantId?: Assistant["_id"];
|
25 |
}
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { ObjectId } from "mongodb";
|
2 |
+
import type { User } from "./User";
|
3 |
+
import type { Assistant } from "./Assistant";
|
4 |
+
import type { Timestamps } from "./Timestamps";
|
5 |
+
|
6 |
+
export interface Report extends Timestamps {
|
7 |
+
_id: ObjectId;
|
8 |
+
createdBy: User["_id"] | string;
|
9 |
+
assistantId: Assistant["_id"];
|
10 |
+
}
|
@@ -1,4 +1,5 @@
|
|
1 |
import { defaultModel } from "$lib/server/models";
|
|
|
2 |
import type { Timestamps } from "./Timestamps";
|
3 |
import type { User } from "./User";
|
4 |
|
@@ -18,6 +19,8 @@ export interface Settings extends Timestamps {
|
|
18 |
|
19 |
// model name and system prompts
|
20 |
customPrompts?: Record<string, string>;
|
|
|
|
|
21 |
}
|
22 |
|
23 |
// TODO: move this to a constant file along with other constants
|
@@ -25,4 +28,6 @@ export const DEFAULT_SETTINGS = {
|
|
25 |
shareConversationsWithModelAuthors: true,
|
26 |
activeModel: defaultModel.id,
|
27 |
hideEmojiOnSidebar: false,
|
|
|
|
|
28 |
};
|
|
|
1 |
import { defaultModel } from "$lib/server/models";
|
2 |
+
import type { Assistant } from "./Assistant";
|
3 |
import type { Timestamps } from "./Timestamps";
|
4 |
import type { User } from "./User";
|
5 |
|
|
|
19 |
|
20 |
// model name and system prompts
|
21 |
customPrompts?: Record<string, string>;
|
22 |
+
|
23 |
+
assistants?: Assistant["_id"][];
|
24 |
}
|
25 |
|
26 |
// TODO: move this to a constant file along with other constants
|
|
|
28 |
shareConversationsWithModelAuthors: true,
|
29 |
activeModel: defaultModel.id,
|
30 |
hideEmojiOnSidebar: false,
|
31 |
+
customPrompts: {},
|
32 |
+
assistants: [],
|
33 |
};
|
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import type { Message } from "./Message";
|
2 |
import type { Timestamps } from "./Timestamps";
|
3 |
|
@@ -12,4 +13,5 @@ export interface SharedConversation extends Timestamps {
|
|
12 |
title: string;
|
13 |
messages: Message[];
|
14 |
preprompt?: string;
|
|
|
15 |
}
|
|
|
1 |
+
import type { Assistant } from "./Assistant";
|
2 |
import type { Message } from "./Message";
|
3 |
import type { Timestamps } from "./Timestamps";
|
4 |
|
|
|
13 |
title: string;
|
14 |
messages: Message[];
|
15 |
preprompt?: string;
|
16 |
+
assistantId?: Assistant["_id"];
|
17 |
}
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export const timeout = <T>(prom: Promise<T>, time: number): Promise<T> => {
|
2 |
+
let timer: NodeJS.Timeout;
|
3 |
+
return Promise.race([prom, new Promise<T>((_r, rej) => (timer = setTimeout(rej, time)))]).finally(
|
4 |
+
() => clearTimeout(timer)
|
5 |
+
);
|
6 |
+
};
|
@@ -12,16 +12,22 @@ import {
|
|
12 |
MESSAGES_BEFORE_LOGIN,
|
13 |
YDC_API_KEY,
|
14 |
USE_LOCAL_WEBSEARCH,
|
|
|
15 |
} from "$env/static/private";
|
|
|
|
|
16 |
|
17 |
export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
18 |
-
const { conversations } = collections;
|
19 |
depends(UrlDependency.ConversationList);
|
20 |
|
21 |
const settings = await collections.settings.findOne(authCondition(locals));
|
22 |
|
23 |
// If the active model in settings is not valid, set it to the default model. This can happen if model was disabled.
|
24 |
-
if (
|
|
|
|
|
|
|
|
|
25 |
settings.activeModel = defaultModel.id;
|
26 |
await collections.settings.updateOne(authCondition(locals), {
|
27 |
$set: { activeModel: defaultModel.id },
|
@@ -42,7 +48,7 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
42 |
// get the number of messages where `from === "assistant"` across all conversations.
|
43 |
const totalMessages =
|
44 |
(
|
45 |
-
await conversations
|
46 |
.aggregate([
|
47 |
{ $match: authCondition(locals) },
|
48 |
{ $project: { messages: 1 } },
|
@@ -59,33 +65,61 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
59 |
|
60 |
const loginRequired = requiresUser && !locals.user && userHasExceededMessages;
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
return {
|
63 |
-
conversations:
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
id: conv._id.toString(),
|
83 |
-
title: settings?.hideEmojiOnSidebar ? conv.title.replace(/\p{Emoji}/gu, "") : conv.title,
|
84 |
-
model: conv.model ?? defaultModel,
|
85 |
-
updatedAt: conv.updatedAt,
|
86 |
-
};
|
87 |
-
})
|
88 |
-
.toArray(),
|
89 |
settings: {
|
90 |
searchEnabled: !!(
|
91 |
SERPAPI_KEY ||
|
@@ -102,6 +136,7 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
102 |
settings?.shareConversationsWithModelAuthors ??
|
103 |
DEFAULT_SETTINGS.shareConversationsWithModelAuthors,
|
104 |
customPrompts: settings?.customPrompts ?? {},
|
|
|
105 |
},
|
106 |
models: models.map((model) => ({
|
107 |
id: model.id,
|
@@ -120,10 +155,13 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
120 |
})),
|
121 |
oldModels,
|
122 |
user: locals.user && {
|
|
|
123 |
username: locals.user.username,
|
124 |
avatarUrl: locals.user.avatarUrl,
|
125 |
email: locals.user.email,
|
126 |
},
|
|
|
|
|
127 |
loginRequired,
|
128 |
loginEnabled: requiresUser,
|
129 |
guestMode: requiresUser && messagesBeforeLogin > 0,
|
|
|
12 |
MESSAGES_BEFORE_LOGIN,
|
13 |
YDC_API_KEY,
|
14 |
USE_LOCAL_WEBSEARCH,
|
15 |
+
ENABLE_ASSISTANTS,
|
16 |
} from "$env/static/private";
|
17 |
+
import { ObjectId } from "mongodb";
|
18 |
+
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
19 |
|
20 |
export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
|
21 |
depends(UrlDependency.ConversationList);
|
22 |
|
23 |
const settings = await collections.settings.findOne(authCondition(locals));
|
24 |
|
25 |
// If the active model in settings is not valid, set it to the default model. This can happen if model was disabled.
|
26 |
+
if (
|
27 |
+
settings &&
|
28 |
+
!validateModel(models).safeParse(settings?.activeModel).success &&
|
29 |
+
!settings.assistants?.map((el) => el.toString())?.includes(settings?.activeModel)
|
30 |
+
) {
|
31 |
settings.activeModel = defaultModel.id;
|
32 |
await collections.settings.updateOne(authCondition(locals), {
|
33 |
$set: { activeModel: defaultModel.id },
|
|
|
48 |
// get the number of messages where `from === "assistant"` across all conversations.
|
49 |
const totalMessages =
|
50 |
(
|
51 |
+
await collections.conversations
|
52 |
.aggregate([
|
53 |
{ $match: authCondition(locals) },
|
54 |
{ $project: { messages: 1 } },
|
|
|
65 |
|
66 |
const loginRequired = requiresUser && !locals.user && userHasExceededMessages;
|
67 |
|
68 |
+
const enableAssistants = ENABLE_ASSISTANTS === "true";
|
69 |
+
|
70 |
+
const assistantActive = !models.map(({ id }) => id).includes(settings?.activeModel ?? "");
|
71 |
+
|
72 |
+
const assistant = assistantActive
|
73 |
+
? JSON.parse(
|
74 |
+
JSON.stringify(
|
75 |
+
await collections.assistants.findOne({
|
76 |
+
_id: new ObjectId(settings?.activeModel),
|
77 |
+
})
|
78 |
+
)
|
79 |
+
)
|
80 |
+
: null;
|
81 |
+
|
82 |
+
const conversations = await collections.conversations
|
83 |
+
.find(authCondition(locals))
|
84 |
+
.sort({ updatedAt: -1 })
|
85 |
+
.project<
|
86 |
+
Pick<Conversation, "title" | "model" | "_id" | "updatedAt" | "createdAt" | "assistantId">
|
87 |
+
>({
|
88 |
+
title: 1,
|
89 |
+
model: 1,
|
90 |
+
_id: 1,
|
91 |
+
updatedAt: 1,
|
92 |
+
createdAt: 1,
|
93 |
+
assistantId: 1,
|
94 |
+
})
|
95 |
+
.toArray();
|
96 |
+
|
97 |
+
const assistantIds = conversations
|
98 |
+
.map((conv) => conv.assistantId)
|
99 |
+
.filter((el) => !!el) as ObjectId[];
|
100 |
+
|
101 |
+
const assistants = await collections.assistants.find({ _id: { $in: assistantIds } }).toArray();
|
102 |
+
|
103 |
return {
|
104 |
+
conversations: conversations.map((conv) => {
|
105 |
+
if (settings?.hideEmojiOnSidebar) {
|
106 |
+
conv.title = conv.title.replace(/\p{Emoji}/gu, "");
|
107 |
+
}
|
108 |
+
|
109 |
+
// remove invalid unicode and trim whitespaces
|
110 |
+
conv.title = conv.title.replace(/\uFFFD/gu, "").trimStart();
|
111 |
+
|
112 |
+
return {
|
113 |
+
id: conv._id.toString(),
|
114 |
+
title: conv.title,
|
115 |
+
model: conv.model ?? defaultModel,
|
116 |
+
updatedAt: conv.updatedAt,
|
117 |
+
assistantId: conv.assistantId?.toString(),
|
118 |
+
avatarHash:
|
119 |
+
conv.assistantId &&
|
120 |
+
assistants.find((a) => a._id.toString() === conv.assistantId?.toString())?.avatar,
|
121 |
+
};
|
122 |
+
}) satisfies ConvSidebar[],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
settings: {
|
124 |
searchEnabled: !!(
|
125 |
SERPAPI_KEY ||
|
|
|
136 |
settings?.shareConversationsWithModelAuthors ??
|
137 |
DEFAULT_SETTINGS.shareConversationsWithModelAuthors,
|
138 |
customPrompts: settings?.customPrompts ?? {},
|
139 |
+
assistants: settings?.assistants?.map((el) => el.toString()) ?? [],
|
140 |
},
|
141 |
models: models.map((model) => ({
|
142 |
id: model.id,
|
|
|
155 |
})),
|
156 |
oldModels,
|
157 |
user: locals.user && {
|
158 |
+
id: locals.user._id.toString(),
|
159 |
username: locals.user.username,
|
160 |
avatarUrl: locals.user.avatarUrl,
|
161 |
email: locals.user.email,
|
162 |
},
|
163 |
+
assistant,
|
164 |
+
enableAssistants,
|
165 |
loginRequired,
|
166 |
loginEnabled: requiresUser,
|
167 |
guestMode: requiresUser && messagesBeforeLogin > 0,
|
@@ -4,7 +4,7 @@
|
|
4 |
import { page } from "$app/stores";
|
5 |
import "../styles/main.css";
|
6 |
import { base } from "$app/paths";
|
7 |
-
import { PUBLIC_ORIGIN } from "$env/static/public";
|
8 |
|
9 |
import { shareConversation } from "$lib/shareConversation";
|
10 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
@@ -17,6 +17,7 @@
|
|
17 |
import titleUpdate from "$lib/stores/titleUpdate";
|
18 |
import { createSettingsStore } from "$lib/stores/settings";
|
19 |
import { browser } from "$app/environment";
|
|
|
20 |
|
21 |
export let data;
|
22 |
|
@@ -120,13 +121,19 @@
|
|
120 |
<meta name="description" content="The first open source alternative to ChatGPT. 💪" />
|
121 |
<meta name="twitter:card" content="summary_large_image" />
|
122 |
<meta name="twitter:site" content="@huggingface" />
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
property="og:
|
128 |
-
content="
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
<link
|
131 |
rel="icon"
|
132 |
href="{PUBLIC_ORIGIN || $page.url.origin}{base}/{PUBLIC_APP_ASSETS}/favicon.ico"
|
@@ -147,6 +154,10 @@
|
|
147 |
/>
|
148 |
</svelte:head>
|
149 |
|
|
|
|
|
|
|
|
|
150 |
<div
|
151 |
class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd md:grid-cols-[280px,1fr] md:grid-rows-[1fr] dark:text-gray-300"
|
152 |
>
|
|
|
4 |
import { page } from "$app/stores";
|
5 |
import "../styles/main.css";
|
6 |
import { base } from "$app/paths";
|
7 |
+
import { PUBLIC_APP_DESCRIPTION, PUBLIC_ORIGIN } from "$env/static/public";
|
8 |
|
9 |
import { shareConversation } from "$lib/shareConversation";
|
10 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
|
|
17 |
import titleUpdate from "$lib/stores/titleUpdate";
|
18 |
import { createSettingsStore } from "$lib/stores/settings";
|
19 |
import { browser } from "$app/environment";
|
20 |
+
import DisclaimerModal from "$lib/components/DisclaimerModal.svelte";
|
21 |
|
22 |
export let data;
|
23 |
|
|
|
121 |
<meta name="description" content="The first open source alternative to ChatGPT. 💪" />
|
122 |
<meta name="twitter:card" content="summary_large_image" />
|
123 |
<meta name="twitter:site" content="@huggingface" />
|
124 |
+
|
125 |
+
<!-- use those meta tags everywhere except on the share assistant page -->
|
126 |
+
<!-- feel free to refacto if there's a better way -->
|
127 |
+
{#if !$page.url.pathname.includes("/assistant/")}
|
128 |
+
<meta property="og:title" content={PUBLIC_APP_NAME} />
|
129 |
+
<meta property="og:type" content="website" />
|
130 |
+
<meta property="og:url" content="{PUBLIC_ORIGIN || $page.url.origin}{base}" />
|
131 |
+
<meta
|
132 |
+
property="og:image"
|
133 |
+
content="{PUBLIC_ORIGIN || $page.url.origin}{base}/{PUBLIC_APP_ASSETS}/thumbnail.png"
|
134 |
+
/>
|
135 |
+
<meta property="og:description" content={PUBLIC_APP_DESCRIPTION} />
|
136 |
+
{/if}
|
137 |
<link
|
138 |
rel="icon"
|
139 |
href="{PUBLIC_ORIGIN || $page.url.origin}{base}/{PUBLIC_APP_ASSETS}/favicon.ico"
|
|
|
154 |
/>
|
155 |
</svelte:head>
|
156 |
|
157 |
+
{#if !$settings.ethicsModalAccepted}
|
158 |
+
<DisclaimerModal />
|
159 |
+
{/if}
|
160 |
+
|
161 |
<div
|
162 |
class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd md:grid-cols-[280px,1fr] md:grid-rows-[1fr] dark:text-gray-300"
|
163 |
>
|
@@ -17,14 +17,32 @@
|
|
17 |
async function createConversation(message: string) {
|
18 |
try {
|
19 |
loading = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
const res = await fetch(`${base}/conversation`, {
|
21 |
method: "POST",
|
22 |
headers: {
|
23 |
"Content-Type": "application/json",
|
24 |
},
|
25 |
body: JSON.stringify({
|
26 |
-
model
|
27 |
preprompt: $settings.customPrompts[$settings.activeModel],
|
|
|
28 |
}),
|
29 |
});
|
30 |
|
@@ -60,6 +78,7 @@
|
|
60 |
<ChatWindow
|
61 |
on:message={(ev) => createConversation(ev.detail)}
|
62 |
{loading}
|
|
|
63 |
currentModel={findCurrentModel([...data.models, ...data.oldModels], $settings.activeModel)}
|
64 |
models={data.models}
|
65 |
bind:files
|
|
|
17 |
async function createConversation(message: string) {
|
18 |
try {
|
19 |
loading = true;
|
20 |
+
|
21 |
+
// check if $settings.activeModel is a valid model
|
22 |
+
// else check if it's an assistant, and use that model
|
23 |
+
// else use the first model
|
24 |
+
|
25 |
+
const validModels = data.models.map((model) => model.id);
|
26 |
+
|
27 |
+
let model;
|
28 |
+
if (validModels.includes($settings.activeModel)) {
|
29 |
+
model = $settings.activeModel;
|
30 |
+
} else {
|
31 |
+
if (validModels.includes(data.assistant?.modelId)) {
|
32 |
+
model = data.assistant?.modelId;
|
33 |
+
} else {
|
34 |
+
model = data.models[0].id;
|
35 |
+
}
|
36 |
+
}
|
37 |
const res = await fetch(`${base}/conversation`, {
|
38 |
method: "POST",
|
39 |
headers: {
|
40 |
"Content-Type": "application/json",
|
41 |
},
|
42 |
body: JSON.stringify({
|
43 |
+
model,
|
44 |
preprompt: $settings.customPrompts[$settings.activeModel],
|
45 |
+
assistantId: data.assistant?._id,
|
46 |
}),
|
47 |
});
|
48 |
|
|
|
78 |
<ChatWindow
|
79 |
on:message={(ev) => createConversation(ev.detail)}
|
80 |
{loading}
|
81 |
+
assistant={data.assistant}
|
82 |
currentModel={findCurrentModel([...data.models, ...data.oldModels], $settings.activeModel)}
|
83 |
models={data.models}
|
84 |
bind:files
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { base } from "$app/paths";
|
2 |
+
import { collections } from "$lib/server/database.js";
|
3 |
+
import { redirect } from "@sveltejs/kit";
|
4 |
+
import { ObjectId } from "mongodb";
|
5 |
+
|
6 |
+
export const load = async ({ params }) => {
|
7 |
+
try {
|
8 |
+
const assistant = await collections.assistants.findOne({
|
9 |
+
_id: new ObjectId(params.assistantId),
|
10 |
+
});
|
11 |
+
|
12 |
+
if (!assistant) {
|
13 |
+
throw redirect(302, `${base}`);
|
14 |
+
}
|
15 |
+
|
16 |
+
return { assistant: JSON.parse(JSON.stringify(assistant)) };
|
17 |
+
} catch {
|
18 |
+
throw redirect(302, `${base}`);
|
19 |
+
}
|
20 |
+
};
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { base } from "$app/paths";
|
3 |
+
import { clickOutside } from "$lib/actions/clickOutside";
|
4 |
+
import { afterNavigate, goto } from "$app/navigation";
|
5 |
+
|
6 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
7 |
+
import type { PageData } from "./$types";
|
8 |
+
import { applyAction, enhance } from "$app/forms";
|
9 |
+
import { PUBLIC_APP_NAME, PUBLIC_ORIGIN } from "$env/static/public";
|
10 |
+
import { page } from "$app/stores";
|
11 |
+
|
12 |
+
export let data: PageData;
|
13 |
+
|
14 |
+
let previousPage: string = base;
|
15 |
+
|
16 |
+
afterNavigate(({ from }) => {
|
17 |
+
if (!from?.url.pathname.includes("settings")) {
|
18 |
+
previousPage = from?.url.pathname || previousPage;
|
19 |
+
}
|
20 |
+
});
|
21 |
+
|
22 |
+
const settings = useSettingsStore();
|
23 |
+
</script>
|
24 |
+
|
25 |
+
<svelte:head>
|
26 |
+
<meta property="og:title" content={data.assistant.name + " - " + PUBLIC_APP_NAME} />
|
27 |
+
<meta property="og:type" content="link" />
|
28 |
+
<meta
|
29 |
+
property="og:description"
|
30 |
+
content={`Use the ${data.assistant.name} assistant inside of ${PUBLIC_APP_NAME}`}
|
31 |
+
/>
|
32 |
+
<meta
|
33 |
+
property="og:image"
|
34 |
+
content="{PUBLIC_ORIGIN || $page.url.origin}{base}/assistant/{data.assistant._id}/thumbnail.png"
|
35 |
+
/>
|
36 |
+
<meta property="og:url" content={$page.url.href} />
|
37 |
+
<meta name="twitter:card" content="summary_large_image" />
|
38 |
+
</svelte:head>
|
39 |
+
|
40 |
+
<div
|
41 |
+
class="fixed inset-0 flex items-center justify-center bg-black/80 backdrop-blur-sm dark:bg-black/50"
|
42 |
+
>
|
43 |
+
<dialog
|
44 |
+
open
|
45 |
+
use:clickOutside={() => {
|
46 |
+
goto(previousPage);
|
47 |
+
}}
|
48 |
+
class="z-10 flex flex-col content-center items-center gap-x-10 gap-y-2 overflow-hidden rounded-2xl bg-white p-4 text-center shadow-2xl outline-none max-sm:px-6 md:w-96 md:grid-cols-3 md:grid-rows-[auto,1fr] md:p-8"
|
49 |
+
>
|
50 |
+
{#if data.assistant.avatar}
|
51 |
+
<img
|
52 |
+
class="h-24 w-24 rounded-full object-cover"
|
53 |
+
src="{base}/settings/assistants/{data.assistant._id}/avatar?hash={data.assistant.avatar}"
|
54 |
+
alt="avatar"
|
55 |
+
/>
|
56 |
+
{:else}
|
57 |
+
<div
|
58 |
+
class="flex h-24 w-24 items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500"
|
59 |
+
>
|
60 |
+
{data.assistant.name[0]}
|
61 |
+
</div>
|
62 |
+
{/if}
|
63 |
+
<h1 class="text-2xl font-bold">
|
64 |
+
{data.assistant.name}
|
65 |
+
</h1>
|
66 |
+
<h3 class="text-sm text-gray-700">
|
67 |
+
{data.assistant.description}
|
68 |
+
</h3>
|
69 |
+
{#if data.assistant.createdByName}
|
70 |
+
<p class="text-sm text-gray-500">
|
71 |
+
Created by <a
|
72 |
+
class="hover:underline"
|
73 |
+
href="https://hf.co/{data.assistant.createdByName}"
|
74 |
+
target="_blank"
|
75 |
+
>
|
76 |
+
{data.assistant.createdByName}
|
77 |
+
</a>
|
78 |
+
</p>
|
79 |
+
{/if}
|
80 |
+
<button
|
81 |
+
class="mt-4 w-full rounded-full bg-gray-200 px-4 py-2 font-semibold text-gray-700"
|
82 |
+
on:click={() => {
|
83 |
+
goto(previousPage);
|
84 |
+
}}
|
85 |
+
>
|
86 |
+
Cancel
|
87 |
+
</button>
|
88 |
+
<form
|
89 |
+
method="POST"
|
90 |
+
action="{base}/settings/assistants/{data.assistant._id}?/subscribe"
|
91 |
+
class="w-full"
|
92 |
+
use:enhance={() => {
|
93 |
+
return async ({ result }) => {
|
94 |
+
// `result` is an `ActionResult` object
|
95 |
+
if (result.type === "success") {
|
96 |
+
$settings.activeModel = data.assistant._id;
|
97 |
+
goto(`${base}`);
|
98 |
+
} else {
|
99 |
+
await applyAction(result);
|
100 |
+
}
|
101 |
+
};
|
102 |
+
}}
|
103 |
+
>
|
104 |
+
<button
|
105 |
+
type="submit"
|
106 |
+
class=" w-full rounded-full bg-black px-4 py-3 font-semibold text-white"
|
107 |
+
>
|
108 |
+
Start chatting
|
109 |
+
</button>
|
110 |
+
</form>
|
111 |
+
</dialog>
|
112 |
+
</div>
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { APP_BASE } from "$env/static/private";
|
2 |
+
import ChatThumbnail from "./ChatThumbnail.svelte";
|
3 |
+
import { collections } from "$lib/server/database";
|
4 |
+
import { error, type RequestHandler } from "@sveltejs/kit";
|
5 |
+
import { ObjectId } from "mongodb";
|
6 |
+
import type { SvelteComponent } from "svelte";
|
7 |
+
|
8 |
+
import { Resvg } from "@resvg/resvg-js";
|
9 |
+
import satori from "satori";
|
10 |
+
import { html } from "satori-html";
|
11 |
+
import { base } from "$app/paths";
|
12 |
+
|
13 |
+
export const GET: RequestHandler = (async ({ url, params, fetch }) => {
|
14 |
+
const assistant = await collections.assistants.findOne({
|
15 |
+
_id: new ObjectId(params.assistantId),
|
16 |
+
});
|
17 |
+
|
18 |
+
if (!assistant) {
|
19 |
+
throw error(404, "Assistant not found.");
|
20 |
+
}
|
21 |
+
|
22 |
+
const renderedComponent = (ChatThumbnail as unknown as SvelteComponent).render({
|
23 |
+
href: url.origin,
|
24 |
+
name: assistant.name,
|
25 |
+
description: assistant.description,
|
26 |
+
createdByName: assistant.createdByName,
|
27 |
+
avatarUrl: assistant.avatar
|
28 |
+
? url.origin + APP_BASE + "/settings/assistants/" + assistant._id + "/avatar"
|
29 |
+
: undefined,
|
30 |
+
});
|
31 |
+
|
32 |
+
const reactLike = html(
|
33 |
+
"<style>" + renderedComponent.css.code + "</style>" + renderedComponent.html
|
34 |
+
);
|
35 |
+
|
36 |
+
const svg = await satori(reactLike, {
|
37 |
+
width: 1200,
|
38 |
+
height: 648,
|
39 |
+
fonts: [
|
40 |
+
{
|
41 |
+
name: "Inter",
|
42 |
+
data: await fetch(base + "/fonts/Inter-Regular.ttf").then((r) => r.arrayBuffer()),
|
43 |
+
weight: 500,
|
44 |
+
},
|
45 |
+
{
|
46 |
+
name: "Inter",
|
47 |
+
data: await fetch(base + "/fonts/Inter-Bold.ttf").then((r) => r.arrayBuffer()),
|
48 |
+
weight: 700,
|
49 |
+
},
|
50 |
+
],
|
51 |
+
});
|
52 |
+
|
53 |
+
const png = new Resvg(svg, {
|
54 |
+
fitTo: { mode: "original" },
|
55 |
+
})
|
56 |
+
.render()
|
57 |
+
.asPng();
|
58 |
+
|
59 |
+
return new Response(png, {
|
60 |
+
headers: {
|
61 |
+
"Content-Type": "image/png",
|
62 |
+
},
|
63 |
+
});
|
64 |
+
}) satisfies RequestHandler;
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { base } from "$app/paths";
|
3 |
+
import { PUBLIC_APP_ASSETS } from "$env/static/public";
|
4 |
+
|
5 |
+
export let href: string = "";
|
6 |
+
export let name: string;
|
7 |
+
export let description: string = "";
|
8 |
+
export let createdByName: string | undefined;
|
9 |
+
export let avatarUrl: string | undefined;
|
10 |
+
|
11 |
+
const imgUrl = `${href}${base}/${PUBLIC_APP_ASSETS}/logo.svg`;
|
12 |
+
</script>
|
13 |
+
|
14 |
+
<div class="flex h-full w-full flex-col items-center justify-center bg-black p-2">
|
15 |
+
<div class="flex w-full max-w-[540px] items-start justify-center text-white">
|
16 |
+
{#if avatarUrl}
|
17 |
+
<img class="h-64 w-64 rounded-full" src={avatarUrl} alt="avatar" />
|
18 |
+
{/if}
|
19 |
+
<div class="ml-10 flex flex-col items-start">
|
20 |
+
<p class="mb-2 mt-0 text-3xl font-normal text-gray-400">
|
21 |
+
<img class="mr-1.5 h-8 w-8" src={imgUrl} alt="app logo" />
|
22 |
+
AI assistant
|
23 |
+
</p>
|
24 |
+
<h1 class="m-0 {name.length < 38 ? 'text-5xl' : 'text-4xl'} text-balance font-black">
|
25 |
+
{name}
|
26 |
+
</h1>
|
27 |
+
<p class="mb-8 text-pretty text-2xl">
|
28 |
+
{description.slice(0, 160)}
|
29 |
+
{#if description.length > 160}...{/if}
|
30 |
+
</p>
|
31 |
+
<div class="rounded-full bg-[#FFA800] px-8 py-3 text-3xl font-semibold text-black">
|
32 |
+
Start chatting
|
33 |
+
</div>
|
34 |
+
</div>
|
35 |
+
</div>
|
36 |
+
{#if createdByName}
|
37 |
+
<p class="absolute bottom-4 right-8 text-2xl text-gray-400">
|
38 |
+
An AI assistant created by {createdByName}
|
39 |
+
</p>
|
40 |
+
{/if}
|
41 |
+
</div>
|
@@ -18,11 +18,11 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
18 |
.object({
|
19 |
fromShare: z.string().optional(),
|
20 |
model: validateModel(models),
|
|
|
21 |
preprompt: z.string().optional(),
|
22 |
})
|
23 |
.parse(JSON.parse(body));
|
24 |
|
25 |
-
let preprompt = values.preprompt;
|
26 |
let embeddingModel: string;
|
27 |
|
28 |
if (values.fromShare) {
|
@@ -37,8 +37,9 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
37 |
title = conversation.title;
|
38 |
messages = conversation.messages;
|
39 |
values.model = conversation.model;
|
|
|
|
|
40 |
embeddingModel = conversation.embeddingModel;
|
41 |
-
preprompt = conversation.preprompt;
|
42 |
}
|
43 |
|
44 |
const model = models.find((m) => m.name === values.model);
|
@@ -54,7 +55,16 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
54 |
}
|
55 |
|
56 |
// Use the model preprompt if there is no conversation/preprompt in the request body
|
57 |
-
preprompt =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
const res = await collections.conversations.insertOne({
|
60 |
_id: new ObjectId(),
|
@@ -62,6 +72,7 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|
62 |
messages,
|
63 |
model: values.model,
|
64 |
preprompt: preprompt === model?.preprompt ? model?.preprompt : preprompt,
|
|
|
65 |
createdAt: new Date(),
|
66 |
updatedAt: new Date(),
|
67 |
embeddingModel,
|
|
|
18 |
.object({
|
19 |
fromShare: z.string().optional(),
|
20 |
model: validateModel(models),
|
21 |
+
assistantId: z.string().optional(),
|
22 |
preprompt: z.string().optional(),
|
23 |
})
|
24 |
.parse(JSON.parse(body));
|
25 |
|
|
|
26 |
let embeddingModel: string;
|
27 |
|
28 |
if (values.fromShare) {
|
|
|
37 |
title = conversation.title;
|
38 |
messages = conversation.messages;
|
39 |
values.model = conversation.model;
|
40 |
+
values.preprompt = conversation.preprompt;
|
41 |
+
values.assistantId = conversation.assistantId?.toString();
|
42 |
embeddingModel = conversation.embeddingModel;
|
|
|
43 |
}
|
44 |
|
45 |
const model = models.find((m) => m.name === values.model);
|
|
|
55 |
}
|
56 |
|
57 |
// Use the model preprompt if there is no conversation/preprompt in the request body
|
58 |
+
const preprompt = await (async () => {
|
59 |
+
if (values.assistantId) {
|
60 |
+
const assistant = await collections.assistants.findOne({
|
61 |
+
_id: new ObjectId(values.assistantId),
|
62 |
+
});
|
63 |
+
return assistant?.preprompt;
|
64 |
+
} else {
|
65 |
+
return values?.preprompt ?? model?.preprompt;
|
66 |
+
}
|
67 |
+
})();
|
68 |
|
69 |
const res = await collections.conversations.insertOne({
|
70 |
_id: new ObjectId(),
|
|
|
72 |
messages,
|
73 |
model: values.model,
|
74 |
preprompt: preprompt === model?.preprompt ? model?.preprompt : preprompt,
|
75 |
+
assistantId: values.assistantId ? new ObjectId(values.assistantId) : undefined,
|
76 |
createdAt: new Date(),
|
77 |
updatedAt: new Date(),
|
78 |
embeddingModel,
|
@@ -44,11 +44,21 @@ export const load = async ({ params, depends, locals }) => {
|
|
44 |
throw error(404, "Conversation not found.");
|
45 |
}
|
46 |
}
|
|
|
47 |
return {
|
48 |
messages: conversation.messages,
|
49 |
title: conversation.title,
|
50 |
model: conversation.model,
|
51 |
preprompt: conversation.preprompt,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
shared,
|
53 |
};
|
54 |
};
|
|
|
44 |
throw error(404, "Conversation not found.");
|
45 |
}
|
46 |
}
|
47 |
+
|
48 |
return {
|
49 |
messages: conversation.messages,
|
50 |
title: conversation.title,
|
51 |
model: conversation.model,
|
52 |
preprompt: conversation.preprompt,
|
53 |
+
assistant: conversation.assistantId
|
54 |
+
? JSON.parse(
|
55 |
+
JSON.stringify(
|
56 |
+
await collections.assistants.findOne({
|
57 |
+
_id: new ObjectId(conversation.assistantId),
|
58 |
+
})
|
59 |
+
)
|
60 |
+
)
|
61 |
+
: null,
|
62 |
shared,
|
63 |
};
|
64 |
};
|
@@ -40,6 +40,7 @@ export async function POST({ params, url, locals }) {
|
|
40 |
model: conversation.model,
|
41 |
embeddingModel: conversation.embeddingModel,
|
42 |
preprompt: conversation.preprompt,
|
|
|
43 |
};
|
44 |
|
45 |
await collections.sharedConversations.insertOne(shared);
|
|
|
40 |
model: conversation.model,
|
41 |
embeddingModel: conversation.embeddingModel,
|
42 |
preprompt: conversation.preprompt,
|
43 |
+
assistantId: conversation.assistantId,
|
44 |
};
|
45 |
|
46 |
await collections.sharedConversations.insertOne(shared);
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { collections } from "$lib/server/database";
|
2 |
+
import { ObjectId } from "mongodb";
|
3 |
+
import type { LayoutServerLoad } from "./$types";
|
4 |
+
|
5 |
+
export const load = (async ({ locals, parent }) => {
|
6 |
+
const { settings } = await parent();
|
7 |
+
|
8 |
+
// find assistants matching the settings assistants
|
9 |
+
const assistants = await collections.assistants
|
10 |
+
.find({
|
11 |
+
_id: { $in: settings.assistants.map((el) => new ObjectId(el)) },
|
12 |
+
})
|
13 |
+
.toArray();
|
14 |
+
|
15 |
+
return {
|
16 |
+
assistants: await Promise.all(
|
17 |
+
assistants.map(async (el) => ({
|
18 |
+
...el,
|
19 |
+
_id: el._id.toString(),
|
20 |
+
createdById: undefined,
|
21 |
+
createdByMe:
|
22 |
+
el.createdById.toString() === (locals.user?._id ?? locals.sessionId).toString(),
|
23 |
+
reported:
|
24 |
+
(await collections.reports.countDocuments({
|
25 |
+
assistantId: el._id,
|
26 |
+
createdBy: locals.user?._id ?? locals.sessionId,
|
27 |
+
})) > 0,
|
28 |
+
}))
|
29 |
+
),
|
30 |
+
};
|
31 |
+
}) satisfies LayoutServerLoad;
|
@@ -1,14 +1,16 @@
|
|
1 |
<script lang="ts">
|
2 |
import { base } from "$app/paths";
|
3 |
import { clickOutside } from "$lib/actions/clickOutside";
|
4 |
-
import { browser } from "$app/environment";
|
5 |
import { afterNavigate, goto } from "$app/navigation";
|
6 |
import { page } from "$app/stores";
|
7 |
import { useSettingsStore } from "$lib/stores/settings";
|
8 |
import CarbonClose from "~icons/carbon/close";
|
9 |
import CarbonCheckmark from "~icons/carbon/checkmark";
|
|
|
10 |
|
11 |
import UserIcon from "~icons/carbon/user";
|
|
|
|
|
12 |
export let data;
|
13 |
|
14 |
let previousPage: string = base;
|
@@ -20,25 +22,27 @@
|
|
20 |
});
|
21 |
|
22 |
const settings = useSettingsStore();
|
|
|
|
|
23 |
</script>
|
24 |
|
25 |
<div
|
26 |
class="fixed inset-0 flex items-center justify-center bg-black/80 backdrop-blur-sm dark:bg-black/50"
|
|
|
27 |
>
|
28 |
<dialog
|
|
|
29 |
open
|
30 |
use:clickOutside={() => {
|
31 |
-
if (browser) window;
|
32 |
goto(previousPage);
|
33 |
}}
|
34 |
-
class="xl: z-10 grid h-[95dvh] w-[90dvw] grid-cols-1 content-start gap-x-
|
35 |
>
|
36 |
-
<div class="col-span-1 flex items-center justify-between md:col-span-3">
|
37 |
<h2 class="text-xl font-bold">Settings</h2>
|
38 |
<button
|
39 |
class="btn rounded-lg"
|
40 |
on:click={() => {
|
41 |
-
if (browser) window;
|
42 |
goto(previousPage);
|
43 |
}}
|
44 |
>
|
@@ -46,38 +50,81 @@
|
|
46 |
</button>
|
47 |
</div>
|
48 |
<div
|
49 |
-
class="col-span-1 flex flex-col overflow-y-auto whitespace-nowrap max-md:-mx-4 max-md:h-[245px] max-md:border md:pr-6"
|
50 |
>
|
|
|
|
|
51 |
{#each data.models.filter((el) => !el.unlisted) as model}
|
52 |
<a
|
53 |
href="{base}/settings/{model.id}"
|
54 |
-
class="group flex h-
|
55 |
-
$page.params.model
|
56 |
-
? '!bg-gray-100 !text-gray-800'
|
57 |
-
: ''}"
|
58 |
>
|
59 |
<div class="truncate">{model.displayName}</div>
|
60 |
{#if model.id === $settings.activeModel}
|
61 |
<div
|
62 |
-
class="rounded-lg bg-black px-2 py-1.5 text-xs font-semibold leading-none text-white"
|
63 |
>
|
64 |
Active
|
65 |
</div>
|
66 |
{/if}
|
67 |
</a>
|
68 |
{/each}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
<a
|
70 |
href="{base}/settings"
|
71 |
-
class="group mt-auto flex h-
|
72 |
-
|
73 |
-
? '!bg-gray-100 !text-gray-800'
|
74 |
-
: ''}"
|
75 |
>
|
76 |
-
<UserIcon class="
|
77 |
Application Settings
|
78 |
</a>
|
79 |
</div>
|
80 |
-
<div class="col-span-1 overflow-y-auto md:col-span-2">
|
81 |
<slot />
|
82 |
</div>
|
83 |
|
@@ -85,7 +132,7 @@
|
|
85 |
<div
|
86 |
class="absolute bottom-4 right-4 m-2 flex items-center gap-1.5 rounded-full border border-gray-300 bg-gray-200 px-3 py-1 text-black"
|
87 |
>
|
88 |
-
<CarbonCheckmark />
|
89 |
Saved
|
90 |
</div>
|
91 |
{/if}
|
|
|
1 |
<script lang="ts">
|
2 |
import { base } from "$app/paths";
|
3 |
import { clickOutside } from "$lib/actions/clickOutside";
|
|
|
4 |
import { afterNavigate, goto } from "$app/navigation";
|
5 |
import { page } from "$app/stores";
|
6 |
import { useSettingsStore } from "$lib/stores/settings";
|
7 |
import CarbonClose from "~icons/carbon/close";
|
8 |
import CarbonCheckmark from "~icons/carbon/checkmark";
|
9 |
+
import CarbonAdd from "~icons/carbon/add";
|
10 |
|
11 |
import UserIcon from "~icons/carbon/user";
|
12 |
+
import { fade, fly } from "svelte/transition";
|
13 |
+
import { PUBLIC_APP_ASSETS } from "$env/static/public";
|
14 |
export let data;
|
15 |
|
16 |
let previousPage: string = base;
|
|
|
22 |
});
|
23 |
|
24 |
const settings = useSettingsStore();
|
25 |
+
|
26 |
+
const isHuggingChat = PUBLIC_APP_ASSETS === "huggingchat";
|
27 |
</script>
|
28 |
|
29 |
<div
|
30 |
class="fixed inset-0 flex items-center justify-center bg-black/80 backdrop-blur-sm dark:bg-black/50"
|
31 |
+
in:fade
|
32 |
>
|
33 |
<dialog
|
34 |
+
in:fly={{ y: 100 }}
|
35 |
open
|
36 |
use:clickOutside={() => {
|
|
|
37 |
goto(previousPage);
|
38 |
}}
|
39 |
+
class="xl: z-10 grid h-[95dvh] w-[90dvw] grid-cols-1 content-start gap-x-8 overflow-hidden rounded-2xl bg-white p-4 shadow-2xl outline-none sm:h-[80dvh] md:grid-cols-3 md:grid-rows-[auto,1fr] md:p-8 xl:w-[1200px] 2xl:h-[70dvh]"
|
40 |
>
|
41 |
+
<div class="col-span-1 mb-4 flex items-center justify-between md:col-span-3">
|
42 |
<h2 class="text-xl font-bold">Settings</h2>
|
43 |
<button
|
44 |
class="btn rounded-lg"
|
45 |
on:click={() => {
|
|
|
46 |
goto(previousPage);
|
47 |
}}
|
48 |
>
|
|
|
50 |
</button>
|
51 |
</div>
|
52 |
<div
|
53 |
+
class="col-span-1 flex flex-col overflow-y-auto whitespace-nowrap max-md:-mx-4 max-md:h-[245px] max-md:border max-md:border-b-2 md:pr-6"
|
54 |
>
|
55 |
+
<h3 class="pb-3 pl-3 pt-2 text-[.8rem] text-gray-800 sm:pl-1">Models</h3>
|
56 |
+
|
57 |
{#each data.models.filter((el) => !el.unlisted) as model}
|
58 |
<a
|
59 |
href="{base}/settings/{model.id}"
|
60 |
+
class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
|
61 |
+
{model.id === $page.params.model ? '!bg-gray-100 !text-gray-800' : ''}"
|
|
|
|
|
62 |
>
|
63 |
<div class="truncate">{model.displayName}</div>
|
64 |
{#if model.id === $settings.activeModel}
|
65 |
<div
|
66 |
+
class="ml-auto rounded-lg bg-black px-2 py-1.5 text-xs font-semibold leading-none text-white"
|
67 |
>
|
68 |
Active
|
69 |
</div>
|
70 |
{/if}
|
71 |
</a>
|
72 |
{/each}
|
73 |
+
<!-- if its huggingchat, the number of assistants owned by the user must be non-zero to show the UI -->
|
74 |
+
{#if data.enableAssistants && (!isHuggingChat || data.assistants.length >= 1)}
|
75 |
+
<h3 class="pb-3 pl-3 pt-5 text-[.8rem] text-gray-800 sm:pl-1">Assistants</h3>
|
76 |
+
{#each data.assistants as assistant}
|
77 |
+
<a
|
78 |
+
href="{base}/settings/assistants/{assistant._id.toString()}"
|
79 |
+
class="group flex h-10 flex-none items-center gap-2 pl-2 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
|
80 |
+
{assistant._id.toString() === $page.params.assistantId ? '!bg-gray-100 !text-gray-800' : ''}"
|
81 |
+
>
|
82 |
+
{#if assistant.avatar}
|
83 |
+
<img
|
84 |
+
src="{base}/settings/assistants/{assistant._id.toString()}/avatar?hash={assistant.avatar}"
|
85 |
+
alt="Avatar"
|
86 |
+
class="h-6 w-6 rounded-full object-cover"
|
87 |
+
/>
|
88 |
+
{:else}
|
89 |
+
<div
|
90 |
+
class="flex size-6 items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500"
|
91 |
+
>
|
92 |
+
{assistant.name[0]}
|
93 |
+
</div>
|
94 |
+
{/if}
|
95 |
+
<div class="truncate">{assistant.name}</div>
|
96 |
+
{#if assistant._id.toString() === $settings.activeModel}
|
97 |
+
<div
|
98 |
+
class="ml-auto rounded-lg bg-black px-2 py-1.5 text-xs font-semibold leading-none text-white"
|
99 |
+
>
|
100 |
+
Active
|
101 |
+
</div>
|
102 |
+
{/if}
|
103 |
+
</a>
|
104 |
+
{/each}
|
105 |
+
|
106 |
+
{#if !data.loginEnabled || (data.loginEnabled && !!data.user)}
|
107 |
+
<a
|
108 |
+
href="{base}/settings/assistants/new"
|
109 |
+
class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
|
110 |
+
{$page.url.pathname === `${base}/settings/assistants/new` ? '!bg-gray-100 !text-gray-800' : ''}"
|
111 |
+
>
|
112 |
+
<CarbonAdd />
|
113 |
+
<div class="truncate">Create new assistant</div>
|
114 |
+
</a>
|
115 |
+
{/if}
|
116 |
+
{/if}
|
117 |
+
|
118 |
<a
|
119 |
href="{base}/settings"
|
120 |
+
class="group mt-auto flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 max-md:order-first md:rounded-xl
|
121 |
+
{$page.url.pathname === `${base}/settings` ? '!bg-gray-100 !text-gray-800' : ''}"
|
|
|
|
|
122 |
>
|
123 |
+
<UserIcon class="text-lg" />
|
124 |
Application Settings
|
125 |
</a>
|
126 |
</div>
|
127 |
+
<div class="col-span-1 overflow-y-auto px-4 max-md:-mx-4 max-md:pt-6 md:col-span-2">
|
128 |
<slot />
|
129 |
</div>
|
130 |
|
|
|
132 |
<div
|
133 |
class="absolute bottom-4 right-4 m-2 flex items-center gap-1.5 rounded-full border border-gray-300 bg-gray-200 px-3 py-1 text-black"
|
134 |
>
|
135 |
+
<CarbonCheckmark class="text-green-500" />
|
136 |
Saved
|
137 |
</div>
|
138 |
{/if}
|
@@ -1,6 +1,5 @@
|
|
1 |
import { collections } from "$lib/server/database";
|
2 |
import { z } from "zod";
|
3 |
-
import { models, validateModel } from "$lib/server/models";
|
4 |
import { authCondition } from "$lib/server/auth";
|
5 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
6 |
|
@@ -14,7 +13,7 @@ export async function POST({ request, locals }) {
|
|
14 |
.default(DEFAULT_SETTINGS.shareConversationsWithModelAuthors),
|
15 |
hideEmojiOnSidebar: z.boolean().default(DEFAULT_SETTINGS.hideEmojiOnSidebar),
|
16 |
ethicsModalAccepted: z.boolean().optional(),
|
17 |
-
activeModel:
|
18 |
customPrompts: z.record(z.string()).default({}),
|
19 |
})
|
20 |
.parse(body);
|
|
|
1 |
import { collections } from "$lib/server/database";
|
2 |
import { z } from "zod";
|
|
|
3 |
import { authCondition } from "$lib/server/auth";
|
4 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
5 |
|
|
|
13 |
.default(DEFAULT_SETTINGS.shareConversationsWithModelAuthors),
|
14 |
hideEmojiOnSidebar: z.boolean().default(DEFAULT_SETTINGS.hideEmojiOnSidebar),
|
15 |
ethicsModalAccepted: z.boolean().optional(),
|
16 |
+
activeModel: z.string().default(DEFAULT_SETTINGS.activeModel),
|
17 |
customPrompts: z.record(z.string()).default({}),
|
18 |
})
|
19 |
.parse(body);
|
@@ -78,7 +78,7 @@
|
|
78 |
value="{PUBLIC_ORIGIN || $page.url.origin}{base}?model={model.id}"
|
79 |
classNames="!border-none !shadow-none !py-0 !px-1 !rounded-md"
|
80 |
>
|
81 |
-
<div class="flex items-center gap-1.5">
|
82 |
<CarbonLink />Copy direct link to model
|
83 |
</div>
|
84 |
</CopyToClipBoardBtn>
|
|
|
78 |
value="{PUBLIC_ORIGIN || $page.url.origin}{base}?model={model.id}"
|
79 |
classNames="!border-none !shadow-none !py-0 !px-1 !rounded-md"
|
80 |
>
|
81 |
+
<div class="flex items-center gap-1.5 hover:underline">
|
82 |
<CarbonLink />Copy direct link to model
|
83 |
</div>
|
84 |
</CopyToClipBoardBtn>
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { collections } from "$lib/server/database";
|
2 |
+
import { type Actions, fail, redirect } from "@sveltejs/kit";
|
3 |
+
import { ObjectId } from "mongodb";
|
4 |
+
import { authCondition } from "$lib/server/auth";
|
5 |
+
import { base } from "$app/paths";
|
6 |
+
|
7 |
+
async function assistantOnlyIfAuthor(locals: App.Locals, assistantId?: string) {
|
8 |
+
const assistant = await collections.assistants.findOne({ _id: new ObjectId(assistantId) });
|
9 |
+
|
10 |
+
if (!assistant) {
|
11 |
+
throw Error("Assistant not found");
|
12 |
+
}
|
13 |
+
|
14 |
+
if (assistant.createdById.toString() !== (locals.user?._id ?? locals.sessionId).toString()) {
|
15 |
+
throw Error("You are not the author of this assistant");
|
16 |
+
}
|
17 |
+
|
18 |
+
return assistant;
|
19 |
+
}
|
20 |
+
|
21 |
+
export const actions: Actions = {
|
22 |
+
delete: async ({ params, locals }) => {
|
23 |
+
let assistant;
|
24 |
+
try {
|
25 |
+
assistant = await assistantOnlyIfAuthor(locals, params.assistantId);
|
26 |
+
} catch (e) {
|
27 |
+
return fail(400, { error: true, message: (e as Error).message });
|
28 |
+
}
|
29 |
+
|
30 |
+
await collections.assistants.deleteOne({ _id: assistant._id });
|
31 |
+
|
32 |
+
// and remove it from all users settings
|
33 |
+
await collections.settings.updateMany(
|
34 |
+
{},
|
35 |
+
{
|
36 |
+
$pull: { assistants: assistant._id },
|
37 |
+
}
|
38 |
+
);
|
39 |
+
|
40 |
+
// and delete all avatars
|
41 |
+
const fileCursor = collections.bucket.find({ filename: assistant._id.toString() });
|
42 |
+
|
43 |
+
// Step 2: Delete the existing file if it exists
|
44 |
+
let fileId = await fileCursor.next();
|
45 |
+
while (fileId) {
|
46 |
+
await collections.bucket.delete(fileId._id);
|
47 |
+
fileId = await fileCursor.next();
|
48 |
+
}
|
49 |
+
|
50 |
+
throw redirect(302, `${base}/settings`);
|
51 |
+
},
|
52 |
+
report: async ({ params, locals }) => {
|
53 |
+
// is there already a report from this user for this model ?
|
54 |
+
const report = await collections.reports.findOne({
|
55 |
+
assistantId: new ObjectId(params.assistantId),
|
56 |
+
createdBy: locals.user?._id ?? locals.sessionId,
|
57 |
+
});
|
58 |
+
|
59 |
+
if (report) {
|
60 |
+
return fail(400, { error: true, message: "Already reported" });
|
61 |
+
}
|
62 |
+
|
63 |
+
const { acknowledged } = await collections.reports.insertOne({
|
64 |
+
_id: new ObjectId(),
|
65 |
+
assistantId: new ObjectId(params.assistantId),
|
66 |
+
createdBy: locals.user?._id ?? locals.sessionId,
|
67 |
+
createdAt: new Date(),
|
68 |
+
updatedAt: new Date(),
|
69 |
+
});
|
70 |
+
|
71 |
+
if (!acknowledged) {
|
72 |
+
return fail(500, { error: true, message: "Failed to report assistant" });
|
73 |
+
}
|
74 |
+
return { from: "report", ok: true, message: "Assistant reported" };
|
75 |
+
},
|
76 |
+
|
77 |
+
subscribe: async ({ params, locals }) => {
|
78 |
+
const assistant = await collections.assistants.findOne({
|
79 |
+
_id: new ObjectId(params.assistantId),
|
80 |
+
});
|
81 |
+
|
82 |
+
if (!assistant) {
|
83 |
+
return fail(404, { error: true, message: "Assistant not found" });
|
84 |
+
}
|
85 |
+
|
86 |
+
// don't push if it's already there
|
87 |
+
const settings = await collections.settings.findOne(authCondition(locals));
|
88 |
+
|
89 |
+
if (settings?.assistants?.includes(assistant._id)) {
|
90 |
+
return fail(400, { error: true, message: "Already subscribed" });
|
91 |
+
}
|
92 |
+
|
93 |
+
await collections.settings.updateOne(authCondition(locals), {
|
94 |
+
$push: { assistants: assistant._id },
|
95 |
+
});
|
96 |
+
|
97 |
+
return { from: "subscribe", ok: true, message: "Assistant added" };
|
98 |
+
},
|
99 |
+
|
100 |
+
unsubscribe: async ({ params, locals }) => {
|
101 |
+
const assistant = await collections.assistants.findOne({
|
102 |
+
_id: new ObjectId(params.assistantId),
|
103 |
+
});
|
104 |
+
|
105 |
+
if (!assistant) {
|
106 |
+
return fail(404, { error: true, message: "Assistant not found" });
|
107 |
+
}
|
108 |
+
|
109 |
+
await collections.settings.updateOne(authCondition(locals), {
|
110 |
+
$pull: { assistants: assistant._id },
|
111 |
+
});
|
112 |
+
|
113 |
+
throw redirect(302, `${base}/settings`);
|
114 |
+
},
|
115 |
+
};
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { enhance } from "$app/forms";
|
3 |
+
import { base } from "$app/paths";
|
4 |
+
import { page } from "$app/stores";
|
5 |
+
import { PUBLIC_ORIGIN, PUBLIC_SHARE_PREFIX } from "$env/static/public";
|
6 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
7 |
+
import type { PageData } from "./$types";
|
8 |
+
|
9 |
+
import CarbonPen from "~icons/carbon/pen";
|
10 |
+
import CarbonTrash from "~icons/carbon/trash-can";
|
11 |
+
import CarbonCopy from "~icons/carbon/copy-file";
|
12 |
+
import CarbonFlag from "~icons/carbon/flag";
|
13 |
+
import CarbonLink from "~icons/carbon/link";
|
14 |
+
import CopyToClipBoardBtn from "$lib/components/CopyToClipBoardBtn.svelte";
|
15 |
+
|
16 |
+
export let data: PageData;
|
17 |
+
|
18 |
+
$: assistant = data.assistants.find((el) => el._id.toString() === $page.params.assistantId);
|
19 |
+
|
20 |
+
const settings = useSettingsStore();
|
21 |
+
|
22 |
+
$: isActive = $settings.activeModel === $page.params.assistantId;
|
23 |
+
|
24 |
+
const prefix = PUBLIC_SHARE_PREFIX || `${PUBLIC_ORIGIN || $page.url.origin}${base}`;
|
25 |
+
|
26 |
+
$: shareUrl = `${prefix}/assistant/${assistant?._id}`;
|
27 |
+
</script>
|
28 |
+
|
29 |
+
<div class="flex h-full flex-col gap-2">
|
30 |
+
<div class="flex gap-6">
|
31 |
+
{#if assistant?.avatar}
|
32 |
+
<!-- crop image if not square -->
|
33 |
+
<img
|
34 |
+
src={`${base}/settings/assistants/${assistant?._id}/avatar?hash=${assistant?.avatar}`}
|
35 |
+
alt="Avatar"
|
36 |
+
class="h-24 w-24 rounded-full object-cover"
|
37 |
+
/>
|
38 |
+
{:else}
|
39 |
+
<div
|
40 |
+
class="flex size-16 flex-none items-center justify-center rounded-full bg-gray-300 text-4xl font-semibold uppercase text-gray-500 sm:size-24"
|
41 |
+
>
|
42 |
+
{assistant?.name[0]}
|
43 |
+
</div>
|
44 |
+
{/if}
|
45 |
+
|
46 |
+
<div>
|
47 |
+
<h1 class="text-xl font-semibold">
|
48 |
+
{assistant?.name}
|
49 |
+
</h1>
|
50 |
+
|
51 |
+
{#if assistant?.description}
|
52 |
+
<p class="pb-2 text-sm text-gray-500">
|
53 |
+
{assistant.description}
|
54 |
+
</p>
|
55 |
+
{/if}
|
56 |
+
|
57 |
+
<p class="text-sm text-gray-500">
|
58 |
+
Model: <span class="font-semibold"> {assistant?.modelId} </span>
|
59 |
+
</p>
|
60 |
+
<button
|
61 |
+
class="{isActive
|
62 |
+
? 'bg-gray-100'
|
63 |
+
: 'bg-black text-white'} my-2 flex w-fit items-center rounded-full px-3 py-1"
|
64 |
+
disabled={isActive}
|
65 |
+
name="Activate model"
|
66 |
+
on:click|stopPropagation={() => {
|
67 |
+
$settings.activeModel = $page.params.assistantId;
|
68 |
+
}}
|
69 |
+
>
|
70 |
+
{isActive ? "Active" : "Activate"}
|
71 |
+
</button>
|
72 |
+
</div>
|
73 |
+
</div>
|
74 |
+
|
75 |
+
<div>
|
76 |
+
<h2 class="text-lg font-semibold">Direct URL</h2>
|
77 |
+
|
78 |
+
<p class="pb-2 text-sm text-gray-500">
|
79 |
+
People with this link will be able to use your assistant.
|
80 |
+
{#if !assistant?.createdByMe && assistant?.createdByName}
|
81 |
+
Created by <a
|
82 |
+
class="underline"
|
83 |
+
target="_blank"
|
84 |
+
href={"https://hf.co/" + assistant?.createdByName}
|
85 |
+
>
|
86 |
+
{assistant?.createdByName}
|
87 |
+
</a>
|
88 |
+
{/if}
|
89 |
+
</p>
|
90 |
+
|
91 |
+
<div
|
92 |
+
class="flex flex-row gap-2 rounded-lg border-2 border-gray-200 bg-gray-100 py-2 pl-3 pr-1.5"
|
93 |
+
>
|
94 |
+
<input disabled class="flex-1 truncate bg-inherit" value={shareUrl} />
|
95 |
+
<CopyToClipBoardBtn
|
96 |
+
value={shareUrl}
|
97 |
+
classNames="!border-none !shadow-none !py-0 !px-1 !rounded-md"
|
98 |
+
>
|
99 |
+
<div class="flex items-center gap-1.5 text-gray-500 hover:underline">
|
100 |
+
<CarbonLink />Copy
|
101 |
+
</div>
|
102 |
+
</CopyToClipBoardBtn>
|
103 |
+
</div>
|
104 |
+
</div>
|
105 |
+
|
106 |
+
<!-- <div>
|
107 |
+
<h2 class="mb-2 text-lg font-semibold">Model used</h2>
|
108 |
+
|
109 |
+
<div
|
110 |
+
class="flex flex-row gap-2 rounded-lg border-2 border-gray-200 bg-gray-100 py-2 pl-3 pr-1.5"
|
111 |
+
>
|
112 |
+
<input disabled class="flex-1" value="Model" />
|
113 |
+
</div>
|
114 |
+
</div> -->
|
115 |
+
|
116 |
+
<h2 class="mt-4 text-lg font-semibold">System Instructions</h2>
|
117 |
+
|
118 |
+
<textarea disabled class="h-[8lh] w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
|
119 |
+
>{assistant?.preprompt}</textarea
|
120 |
+
>
|
121 |
+
|
122 |
+
<div class="mt-5 flex gap-4">
|
123 |
+
{#if assistant?.createdByMe}
|
124 |
+
<a href="{base}/settings/assistants/{assistant?._id}/edit" class="underline"
|
125 |
+
><CarbonPen class="mr-1.5 inline" />Edit assistant</a
|
126 |
+
>
|
127 |
+
<form method="POST" action="?/delete" use:enhance>
|
128 |
+
<button type="submit" class="flex items-center underline">
|
129 |
+
<CarbonTrash class="mr-1.5 inline" />Delete assistant</button
|
130 |
+
>
|
131 |
+
</form>
|
132 |
+
{:else}
|
133 |
+
<form method="POST" action="?/unsubscribe" use:enhance>
|
134 |
+
<button type="submit" class="underline">
|
135 |
+
<CarbonTrash class="mr-1.5 inline" />Remove assistant</button
|
136 |
+
>
|
137 |
+
</form>
|
138 |
+
<form method="POST" action="?/edit" use:enhance class="hidden">
|
139 |
+
<button type="submit" class="underline">
|
140 |
+
<CarbonCopy class="mr-1.5 inline" />Duplicate assistant</button
|
141 |
+
>
|
142 |
+
</form>
|
143 |
+
{#if !assistant?.reported}
|
144 |
+
<form method="POST" action="?/report" use:enhance>
|
145 |
+
<button type="submit" class="underline">
|
146 |
+
<CarbonFlag class="mr-1.5 inline" />Report assistant</button
|
147 |
+
>
|
148 |
+
</form>
|
149 |
+
{:else}
|
150 |
+
<button type="button" disabled class="text-gray-700">
|
151 |
+
<CarbonFlag class="mr-1.5 inline" />Reported</button
|
152 |
+
>
|
153 |
+
{/if}
|
154 |
+
{/if}
|
155 |
+
</div>
|
156 |
+
</div>
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { base } from "$app/paths";
|
2 |
+
import { redirect } from "@sveltejs/kit";
|
3 |
+
|
4 |
+
export async function load({ parent, params }) {
|
5 |
+
const data = await parent();
|
6 |
+
|
7 |
+
const assistant = data.settings.assistants.find((id) => id === params.assistantId);
|
8 |
+
|
9 |
+
if (!assistant) {
|
10 |
+
throw redirect(302, `${base}/assistant/${params.assistantId}`);
|
11 |
+
}
|
12 |
+
|
13 |
+
return data;
|
14 |
+
}
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { collections } from "$lib/server/database";
|
2 |
+
import { error, type RequestHandler } from "@sveltejs/kit";
|
3 |
+
import { ObjectId } from "mongodb";
|
4 |
+
|
5 |
+
export const GET: RequestHandler = async ({ params }) => {
|
6 |
+
const assistant = await collections.assistants.findOne({
|
7 |
+
_id: new ObjectId(params.assistantId),
|
8 |
+
});
|
9 |
+
|
10 |
+
if (!assistant) {
|
11 |
+
throw error(404, "No assistant found");
|
12 |
+
}
|
13 |
+
|
14 |
+
if (!assistant.avatar) {
|
15 |
+
throw error(404, "No avatar found");
|
16 |
+
}
|
17 |
+
|
18 |
+
const fileId = collections.bucket.find({ filename: assistant._id.toString() });
|
19 |
+
|
20 |
+
let mime = "";
|
21 |
+
|
22 |
+
const content = await fileId.next().then(async (file) => {
|
23 |
+
mime = file?.metadata?.mime;
|
24 |
+
|
25 |
+
if (!file?._id) {
|
26 |
+
throw error(404, "Avatar not found");
|
27 |
+
}
|
28 |
+
|
29 |
+
const fileStream = collections.bucket.openDownloadStream(file?._id);
|
30 |
+
|
31 |
+
const fileBuffer = await new Promise<Buffer>((resolve, reject) => {
|
32 |
+
const chunks: Uint8Array[] = [];
|
33 |
+
fileStream.on("data", (chunk) => chunks.push(chunk));
|
34 |
+
fileStream.on("error", reject);
|
35 |
+
fileStream.on("end", () => resolve(Buffer.concat(chunks)));
|
36 |
+
});
|
37 |
+
|
38 |
+
return fileBuffer;
|
39 |
+
});
|
40 |
+
|
41 |
+
return new Response(content, {
|
42 |
+
headers: {
|
43 |
+
"Content-Type": mime ?? "application/octet-stream",
|
44 |
+
},
|
45 |
+
});
|
46 |
+
};
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { base } from "$app/paths";
|
2 |
+
import { requiresUser } from "$lib/server/auth";
|
3 |
+
import { collections } from "$lib/server/database";
|
4 |
+
import { fail, type Actions, redirect } from "@sveltejs/kit";
|
5 |
+
import { ObjectId } from "mongodb";
|
6 |
+
|
7 |
+
import { z } from "zod";
|
8 |
+
import sizeof from "image-size";
|
9 |
+
import { sha256 } from "$lib/utils/sha256";
|
10 |
+
|
11 |
+
const newAsssistantSchema = z.object({
|
12 |
+
name: z.string().min(1),
|
13 |
+
modelId: z.string().min(1),
|
14 |
+
preprompt: z.string().min(1),
|
15 |
+
description: z.string().optional(),
|
16 |
+
exampleInput1: z.string().optional(),
|
17 |
+
exampleInput2: z.string().optional(),
|
18 |
+
exampleInput3: z.string().optional(),
|
19 |
+
exampleInput4: z.string().optional(),
|
20 |
+
avatar: z.union([z.instanceof(File), z.literal("null")]).optional(),
|
21 |
+
});
|
22 |
+
|
23 |
+
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
24 |
+
const hash = await sha256(await avatar.text());
|
25 |
+
const upload = collections.bucket.openUploadStream(`${assistantId.toString()}`, {
|
26 |
+
metadata: { type: avatar.type, hash },
|
27 |
+
});
|
28 |
+
|
29 |
+
upload.write((await avatar.arrayBuffer()) as unknown as Buffer);
|
30 |
+
upload.end();
|
31 |
+
|
32 |
+
// only return the filename when upload throws a finish event or a 10s time out occurs
|
33 |
+
return new Promise((resolve, reject) => {
|
34 |
+
upload.once("finish", () => resolve(hash));
|
35 |
+
upload.once("error", reject);
|
36 |
+
setTimeout(() => reject(new Error("Upload timed out")), 10000);
|
37 |
+
});
|
38 |
+
};
|
39 |
+
|
40 |
+
export const actions: Actions = {
|
41 |
+
default: async ({ request, locals, params }) => {
|
42 |
+
const assistant = await collections.assistants.findOne({
|
43 |
+
_id: new ObjectId(params.assistantId),
|
44 |
+
});
|
45 |
+
|
46 |
+
if (!assistant) {
|
47 |
+
throw Error("Assistant not found");
|
48 |
+
}
|
49 |
+
|
50 |
+
if (assistant.createdById.toString() !== (locals.user?._id ?? locals.sessionId).toString()) {
|
51 |
+
throw Error("You are not the author of this assistant");
|
52 |
+
}
|
53 |
+
|
54 |
+
const formData = Object.fromEntries(await request.formData());
|
55 |
+
|
56 |
+
const parse = newAsssistantSchema.safeParse(formData);
|
57 |
+
|
58 |
+
if (!parse.success) {
|
59 |
+
// Loop through the errors array and create a custom errors array
|
60 |
+
const errors = parse.error.errors.map((error) => {
|
61 |
+
return {
|
62 |
+
field: error.path[0],
|
63 |
+
message: error.message,
|
64 |
+
};
|
65 |
+
});
|
66 |
+
|
67 |
+
return fail(400, { error: true, errors });
|
68 |
+
}
|
69 |
+
|
70 |
+
// can only create assistants when logged in, IF login is setup
|
71 |
+
if (!locals.user && requiresUser) {
|
72 |
+
const errors = [{ field: "preprompt", message: "Must be logged in. Unauthorized" }];
|
73 |
+
return fail(400, { error: true, errors });
|
74 |
+
}
|
75 |
+
|
76 |
+
const exampleInputs: string[] = [
|
77 |
+
parse?.data?.exampleInput1 ?? "",
|
78 |
+
parse?.data?.exampleInput2 ?? "",
|
79 |
+
parse?.data?.exampleInput3 ?? "",
|
80 |
+
parse?.data?.exampleInput4 ?? "",
|
81 |
+
].filter((input) => !!input);
|
82 |
+
|
83 |
+
const deleteAvatar = parse.data.avatar === "null";
|
84 |
+
|
85 |
+
let hash;
|
86 |
+
if (parse.data.avatar && parse.data.avatar !== "null" && parse.data.avatar.size > 0) {
|
87 |
+
const dims = sizeof(Buffer.from(await parse.data.avatar.arrayBuffer()));
|
88 |
+
|
89 |
+
if ((dims.height ?? 1000) > 512 || (dims.width ?? 1000) > 512) {
|
90 |
+
const errors = [{ field: "avatar", message: "Avatar too big" }];
|
91 |
+
return fail(400, { error: true, errors });
|
92 |
+
}
|
93 |
+
|
94 |
+
const fileCursor = collections.bucket.find({ filename: assistant._id.toString() });
|
95 |
+
|
96 |
+
// Step 2: Delete the existing file if it exists
|
97 |
+
let fileId = await fileCursor.next();
|
98 |
+
while (fileId) {
|
99 |
+
await collections.bucket.delete(fileId._id);
|
100 |
+
fileId = await fileCursor.next();
|
101 |
+
}
|
102 |
+
|
103 |
+
hash = await uploadAvatar(parse.data.avatar, assistant._id);
|
104 |
+
} else if (deleteAvatar) {
|
105 |
+
// delete the avatar
|
106 |
+
const fileCursor = collections.bucket.find({ filename: assistant._id.toString() });
|
107 |
+
|
108 |
+
let fileId = await fileCursor.next();
|
109 |
+
while (fileId) {
|
110 |
+
await collections.bucket.delete(fileId._id);
|
111 |
+
fileId = await fileCursor.next();
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
const { acknowledged } = await collections.assistants.replaceOne(
|
116 |
+
{
|
117 |
+
_id: assistant._id,
|
118 |
+
},
|
119 |
+
{
|
120 |
+
createdById: assistant?.createdById,
|
121 |
+
createdByName: locals.user?.username ?? locals.user?.name,
|
122 |
+
...parse.data,
|
123 |
+
exampleInputs,
|
124 |
+
avatar: deleteAvatar ? undefined : hash ?? assistant.avatar,
|
125 |
+
createdAt: new Date(),
|
126 |
+
updatedAt: new Date(),
|
127 |
+
}
|
128 |
+
);
|
129 |
+
|
130 |
+
if (acknowledged) {
|
131 |
+
throw redirect(302, `${base}/settings/assistants/${assistant._id}`);
|
132 |
+
} else {
|
133 |
+
throw Error("Update failed");
|
134 |
+
}
|
135 |
+
},
|
136 |
+
};
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import type { PageData, ActionData } from "./$types";
|
3 |
+
import { page } from "$app/stores";
|
4 |
+
import AssistantSettings from "$lib/components/AssistantSettings.svelte";
|
5 |
+
|
6 |
+
export let data: PageData;
|
7 |
+
export let form: ActionData;
|
8 |
+
|
9 |
+
$: assistant = data.assistants.find((el) => el._id.toString() === $page.params.assistantId);
|
10 |
+
</script>
|
11 |
+
|
12 |
+
<AssistantSettings bind:form {assistant} models={data.models} />
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { base } from "$app/paths";
|
2 |
+
import { authCondition, requiresUser } from "$lib/server/auth";
|
3 |
+
import { collections } from "$lib/server/database";
|
4 |
+
import { fail, type Actions, redirect } from "@sveltejs/kit";
|
5 |
+
import { ObjectId } from "mongodb";
|
6 |
+
|
7 |
+
import { z } from "zod";
|
8 |
+
import sizeof from "image-size";
|
9 |
+
import { sha256 } from "$lib/utils/sha256";
|
10 |
+
|
11 |
+
const newAsssistantSchema = z.object({
|
12 |
+
name: z.string().min(1),
|
13 |
+
modelId: z.string().min(1),
|
14 |
+
preprompt: z.string().min(1),
|
15 |
+
description: z.string().optional(),
|
16 |
+
exampleInput1: z.string().optional(),
|
17 |
+
exampleInput2: z.string().optional(),
|
18 |
+
exampleInput3: z.string().optional(),
|
19 |
+
exampleInput4: z.string().optional(),
|
20 |
+
avatar: z.instanceof(File).optional(),
|
21 |
+
});
|
22 |
+
|
23 |
+
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
|
24 |
+
const hash = await sha256(await avatar.text());
|
25 |
+
const upload = collections.bucket.openUploadStream(`${assistantId.toString()}`, {
|
26 |
+
metadata: { type: avatar.type, hash },
|
27 |
+
});
|
28 |
+
|
29 |
+
upload.write((await avatar.arrayBuffer()) as unknown as Buffer);
|
30 |
+
upload.end();
|
31 |
+
|
32 |
+
// only return the filename when upload throws a finish event or a 10s time out occurs
|
33 |
+
return new Promise((resolve, reject) => {
|
34 |
+
upload.once("finish", () => resolve(hash));
|
35 |
+
upload.once("error", reject);
|
36 |
+
setTimeout(() => reject(new Error("Upload timed out")), 10000);
|
37 |
+
});
|
38 |
+
};
|
39 |
+
|
40 |
+
export const actions: Actions = {
|
41 |
+
default: async ({ request, locals }) => {
|
42 |
+
const formData = Object.fromEntries(await request.formData());
|
43 |
+
|
44 |
+
const parse = newAsssistantSchema.safeParse(formData);
|
45 |
+
|
46 |
+
if (!parse.success) {
|
47 |
+
// Loop through the errors array and create a custom errors array
|
48 |
+
const errors = parse.error.errors.map((error) => {
|
49 |
+
return {
|
50 |
+
field: error.path[0],
|
51 |
+
message: error.message,
|
52 |
+
};
|
53 |
+
});
|
54 |
+
|
55 |
+
return fail(400, { error: true, errors });
|
56 |
+
}
|
57 |
+
|
58 |
+
// can only create assistants when logged in, IF login is setup
|
59 |
+
if (!locals.user && requiresUser) {
|
60 |
+
const errors = [{ field: "preprompt", message: "Must be logged in. Unauthorized" }];
|
61 |
+
return fail(400, { error: true, errors });
|
62 |
+
}
|
63 |
+
|
64 |
+
const createdById = locals.user?._id ?? locals.sessionId;
|
65 |
+
|
66 |
+
const newAssistantId = new ObjectId();
|
67 |
+
|
68 |
+
const exampleInputs: string[] = [
|
69 |
+
parse?.data?.exampleInput1 ?? "",
|
70 |
+
parse?.data?.exampleInput2 ?? "",
|
71 |
+
parse?.data?.exampleInput3 ?? "",
|
72 |
+
parse?.data?.exampleInput4 ?? "",
|
73 |
+
].filter((input) => !!input);
|
74 |
+
|
75 |
+
let hash;
|
76 |
+
if (parse.data.avatar && parse.data.avatar.size > 0) {
|
77 |
+
const dims = sizeof(Buffer.from(await parse.data.avatar.arrayBuffer()));
|
78 |
+
|
79 |
+
if ((dims.height ?? 1000) > 512 || (dims.width ?? 1000) > 512) {
|
80 |
+
const errors = [
|
81 |
+
{
|
82 |
+
field: "avatar",
|
83 |
+
message:
|
84 |
+
"Avatar is too big. Please make sure the size of your avatar is no bigger than 512px by 512px.",
|
85 |
+
},
|
86 |
+
];
|
87 |
+
return fail(400, { error: true, errors });
|
88 |
+
}
|
89 |
+
|
90 |
+
hash = await uploadAvatar(parse.data.avatar, newAssistantId);
|
91 |
+
}
|
92 |
+
|
93 |
+
const { insertedId } = await collections.assistants.insertOne({
|
94 |
+
_id: newAssistantId,
|
95 |
+
createdById,
|
96 |
+
createdByName: locals.user?.username ?? locals.user?.name,
|
97 |
+
...parse.data,
|
98 |
+
exampleInputs,
|
99 |
+
avatar: hash,
|
100 |
+
createdAt: new Date(),
|
101 |
+
updatedAt: new Date(),
|
102 |
+
});
|
103 |
+
|
104 |
+
// add insertedId to user settings
|
105 |
+
|
106 |
+
await collections.settings.updateOne(authCondition(locals), {
|
107 |
+
$push: { assistants: insertedId },
|
108 |
+
});
|
109 |
+
|
110 |
+
throw redirect(302, `${base}/settings/assistants/${insertedId}`);
|
111 |
+
},
|
112 |
+
};
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import type { ActionData, PageData } from "./$types";
|
3 |
+
import AssistantSettings from "$lib/components/AssistantSettings.svelte";
|
4 |
+
|
5 |
+
export let data: PageData;
|
6 |
+
export let form: ActionData;
|
7 |
+
</script>
|
8 |
+
|
9 |
+
<AssistantSettings bind:form models={data.models} />
|
Binary file (317 kB). View file
|
|
Binary file (317 kB). View file
|
|
Binary file (317 kB). View file
|
|
Binary file (311 kB). View file
|
|
Binary file (311 kB). View file
|
|
Binary file (315 kB). View file
|
|