Spaces:
				
			
			
	
			
			
		Paused
		
	
	
	
			
			
	
	
	
	
		
		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 <mishig.davaadorj@coloradocollege.edu>
* 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 <mishig.davaadorj@coloradocollege.edu>
* add comment to app_base
* Use event modifiers
* Use CSS uppercase instead everywhere
* Update src/lib/components/NavMenu.svelte
Co-authored-by: Mishig <mishig.davaadorj@coloradocollege.edu>
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <mishig.davaadorj@coloradocollege.edu>
* 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 <mishig.davaadorj@coloradocollege.edu>
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <mishig.davaadorj@coloradocollege.edu>
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <mishig.davaadorj@coloradocollege.edu>
* 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 <mikelfried@gmail.com>
* Add timeout & controls to avatar generation
* Add controls for avatar generation in .env
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <mishig.davaadorj@coloradocollege.edu>
* Update src/lib/components/AssistantSettings.svelte
Co-authored-by: Mishig <mishig.davaadorj@coloradocollege.edu>
* Fix avatar gen feature flag
* Can only upload avatar if generate is unchecked
---------
Co-authored-by: Michael Fried <mikelfried@gmail.com>
Co-authored-by: Mishig <mishig.davaadorj@coloradocollege.edu>
* 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 <victor.mustar@gmail.com>
---------
Co-authored-by: Mishig <mishig.davaadorj@coloradocollege.edu>
Co-authored-by: Victor Mustar <victor.mustar@gmail.com>
Co-authored-by: Michael Fried <mikelfried@gmail.com>
- .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 
     | 
| 
         |