Reworked settings menu (#591)
Browse files* initial work on new settings page
* new settings - fully working
* tweak to settings layout
* Added model links
* lint
* redirect on bad model
* delete error page
* fix links & reset button
* add saving indicator
* Make new settings work on mobile
* small tweak mobile
* Fix preprompt so it gets read correctly from the model.preprompt (#595)
* 🔒️ Harden session ID generator (#599)
* bump svelte & related to latest (#600)
* Update dockerfile to node 20 (#601)
* Only refresh cookie on post (#606)
* Session management improvements: Multi sessions, renew on login/logout (#603)
* wip: update sessionId on every login
* comment out object.freeze
* only refresh cookies on post
* Add support for multiple sessions per user
* fix tests
* 🛂 Hash sessionId in DB
* 🐛 do not forget about event.locals.sessionId
* Update src/lib/server/auth.ts
Co-authored-by: Eliott C. <[email protected]>
* Add `expiresAt` field
* remove index causing errors
* Fix bug where sessions were not properly being deleted on logout
* Moved session refresh outside of form content check
---------
Co-authored-by: coyotte508 <[email protected]>
* fix settings custom prompt bug
* simplify
WIP
* mobile
* misc
* mobile
* remove debug log
* Fix switches working only on label
* Revert "Fix switches working only on label"
This reverts commit b46d852316603da660de978d8338261adc6dc07b.
* Switch fix
---------
Co-authored-by: Galén <[email protected]>
Co-authored-by: Eliott C <[email protected]>
Co-authored-by: Victor Mustar <[email protected]>
- package-lock.json +10 -2
- package.json +1 -0
- src/lib/actions/clickOutside.ts +18 -0
- src/lib/components/DisclaimerModal.svelte +22 -32
- src/lib/components/LoginModal.svelte +12 -7
- src/lib/components/ModelsModal.svelte +0 -153
- src/lib/components/NavMenu.svelte +3 -11
- src/lib/components/SettingsModal.svelte +0 -132
- src/lib/components/Switch.svelte +1 -3
- src/lib/components/chat/ChatIntroduction.svelte +5 -12
- src/lib/components/chat/ChatWindow.svelte +7 -7
- src/lib/stores/settings.ts +72 -0
- src/lib/types/Settings.ts +1 -0
- src/routes/+layout.server.ts +5 -4
- src/routes/+layout.svelte +3 -11
- src/routes/+page.svelte +0 -1
- src/routes/conversation/[id]/+page.svelte +1 -2
- src/routes/conversation/[id]/+server.ts +1 -2
- src/routes/settings/+layout.svelte +91 -0
- src/routes/settings/+page.server.ts +0 -53
- src/routes/settings/+page.svelte +90 -0
- src/routes/settings/+server.ts +40 -0
- src/routes/settings/[...model]/+page.svelte +99 -0
- src/routes/settings/[...model]/+page.ts +12 -0
@@ -10,6 +10,7 @@
|
|
10 |
"dependencies": {
|
11 |
"@huggingface/hub": "^0.5.1",
|
12 |
"@huggingface/inference": "^2.6.3",
|
|
|
13 |
"@xenova/transformers": "^2.6.0",
|
14 |
"autoprefixer": "^10.4.14",
|
15 |
"browser-image-resizer": "^2.4.1",
|
@@ -587,6 +588,14 @@
|
|
587 |
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
588 |
"dev": true
|
589 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
590 |
"node_modules/@iconify-json/carbon": {
|
591 |
"version": "1.1.16",
|
592 |
"resolved": "https://registry.npmjs.org/@iconify-json/carbon/-/carbon-1.1.16.tgz",
|
@@ -608,8 +617,7 @@
|
|
608 |
"node_modules/@iconify/types": {
|
609 |
"version": "2.0.0",
|
610 |
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
611 |
-
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="
|
612 |
-
"dev": true
|
613 |
},
|
614 |
"node_modules/@iconify/utils": {
|
615 |
"version": "2.1.5",
|
|
|
10 |
"dependencies": {
|
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",
|
|
|
588 |
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
589 |
"dev": true
|
590 |
},
|
591 |
+
"node_modules/@iconify-json/bi": {
|
592 |
+
"version": "1.1.21",
|
593 |
+
"resolved": "https://registry.npmjs.org/@iconify-json/bi/-/bi-1.1.21.tgz",
|
594 |
+
"integrity": "sha512-6TaRGfIoelS9GBxU4SHkj59pbKliI0WQK4jq2hjuDFE49wrtvREyktOXfyKD11UjMGqx3EpSQKQVEZqaTzmrxA==",
|
595 |
+
"dependencies": {
|
596 |
+
"@iconify/types": "*"
|
597 |
+
}
|
598 |
+
},
|
599 |
"node_modules/@iconify-json/carbon": {
|
600 |
"version": "1.1.16",
|
601 |
"resolved": "https://registry.npmjs.org/@iconify-json/carbon/-/carbon-1.1.16.tgz",
|
|
|
617 |
"node_modules/@iconify/types": {
|
618 |
"version": "2.0.0",
|
619 |
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
620 |
+
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="
|
|
|
621 |
},
|
622 |
"node_modules/@iconify/utils": {
|
623 |
"version": "2.1.5",
|
@@ -46,6 +46,7 @@
|
|
46 |
"dependencies": {
|
47 |
"@huggingface/hub": "^0.5.1",
|
48 |
"@huggingface/inference": "^2.6.3",
|
|
|
49 |
"@xenova/transformers": "^2.6.0",
|
50 |
"autoprefixer": "^10.4.14",
|
51 |
"browser-image-resizer": "^2.4.1",
|
|
|
46 |
"dependencies": {
|
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",
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export function clickOutside(element: HTMLDialogElement, callbackFunction: () => void) {
|
2 |
+
function onClick(event: MouseEvent) {
|
3 |
+
if (!element.contains(event.target as Node)) {
|
4 |
+
callbackFunction();
|
5 |
+
}
|
6 |
+
}
|
7 |
+
|
8 |
+
document.body.addEventListener("click", onClick);
|
9 |
+
|
10 |
+
return {
|
11 |
+
update(newCallbackFunction: () => void) {
|
12 |
+
callbackFunction = newCallbackFunction;
|
13 |
+
},
|
14 |
+
destroy() {
|
15 |
+
document.body.removeEventListener("click", onClick);
|
16 |
+
},
|
17 |
+
};
|
18 |
+
}
|
@@ -4,11 +4,11 @@
|
|
4 |
import { PUBLIC_APP_DESCRIPTION, PUBLIC_APP_NAME } from "$env/static/public";
|
5 |
import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
|
6 |
import Modal from "$lib/components/Modal.svelte";
|
|
|
7 |
import { cookiesAreEnabled } from "$lib/utils/cookiesAreEnabled";
|
8 |
-
import type { LayoutData } from "../../routes/$types";
|
9 |
import Logo from "./icons/Logo.svelte";
|
10 |
|
11 |
-
|
12 |
</script>
|
13 |
|
14 |
<Modal>
|
@@ -31,36 +31,26 @@
|
|
31 |
|
32 |
<div class="flex w-full flex-col items-center gap-2">
|
33 |
{#if $page.data.guestMode || !$page.data.loginEnabled}
|
34 |
-
<
|
35 |
-
|
36 |
-
{
|
37 |
-
|
38 |
-
{
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
}
|
55 |
-
}}
|
56 |
-
>
|
57 |
-
{#if $page.data.loginEnabled}
|
58 |
-
Try as guest
|
59 |
-
{:else}
|
60 |
-
Start chatting
|
61 |
-
{/if}
|
62 |
-
</button>
|
63 |
-
</form>
|
64 |
{/if}
|
65 |
{#if $page.data.loginEnabled}
|
66 |
<form action="{base}/login" target="_parent" method="POST" class="w-full">
|
|
|
4 |
import { PUBLIC_APP_DESCRIPTION, PUBLIC_APP_NAME } from "$env/static/public";
|
5 |
import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
|
6 |
import Modal from "$lib/components/Modal.svelte";
|
7 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
8 |
import { cookiesAreEnabled } from "$lib/utils/cookiesAreEnabled";
|
|
|
9 |
import Logo from "./icons/Logo.svelte";
|
10 |
|
11 |
+
const settings = useSettingsStore();
|
12 |
</script>
|
13 |
|
14 |
<Modal>
|
|
|
31 |
|
32 |
<div class="flex w-full flex-col items-center gap-2">
|
33 |
{#if $page.data.guestMode || !$page.data.loginEnabled}
|
34 |
+
<button
|
35 |
+
class="w-full justify-center rounded-full border-2 border-gray-300 bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
|
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={(e) => {
|
40 |
+
if (!cookiesAreEnabled()) {
|
41 |
+
e.preventDefault();
|
42 |
+
window.open(window.location.href, "_blank");
|
43 |
+
}
|
44 |
+
|
45 |
+
$settings.ethicsModalAccepted = true;
|
46 |
+
}}
|
47 |
+
>
|
48 |
+
{#if $page.data.loginEnabled}
|
49 |
+
Try as guest
|
50 |
+
{:else}
|
51 |
+
Start chatting
|
52 |
+
{/if}
|
53 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
{/if}
|
55 |
{#if $page.data.loginEnabled}
|
56 |
<form action="{base}/login" target="_parent" method="POST" class="w-full">
|
@@ -4,9 +4,11 @@
|
|
4 |
import { PUBLIC_APP_DESCRIPTION, PUBLIC_APP_NAME } from "$env/static/public";
|
5 |
import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
|
6 |
import Modal from "$lib/components/Modal.svelte";
|
7 |
-
import
|
|
|
8 |
import Logo from "./icons/Logo.svelte";
|
9 |
-
|
|
|
10 |
</script>
|
11 |
|
12 |
<Modal on:close>
|
@@ -42,13 +44,16 @@
|
|
42 |
{/if}
|
43 |
</button>
|
44 |
{:else}
|
45 |
-
<input type="hidden" name="ethicsModalAccepted" value={true} />
|
46 |
-
{#each Object.entries(settings) as [key, val]}
|
47 |
-
<input type="hidden" name={key} value={val} />
|
48 |
-
{/each}
|
49 |
<button
|
50 |
-
type="submit"
|
51 |
class="flex w-full items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
>
|
53 |
Start chatting
|
54 |
</button>
|
|
|
4 |
import { PUBLIC_APP_DESCRIPTION, PUBLIC_APP_NAME } from "$env/static/public";
|
5 |
import LogoHuggingFaceBorderless from "$lib/components/icons/LogoHuggingFaceBorderless.svelte";
|
6 |
import Modal from "$lib/components/Modal.svelte";
|
7 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
8 |
+
import { cookiesAreEnabled } from "$lib/utils/cookiesAreEnabled";
|
9 |
import Logo from "./icons/Logo.svelte";
|
10 |
+
|
11 |
+
const settings = useSettingsStore();
|
12 |
</script>
|
13 |
|
14 |
<Modal on:close>
|
|
|
44 |
{/if}
|
45 |
</button>
|
46 |
{:else}
|
|
|
|
|
|
|
|
|
47 |
<button
|
|
|
48 |
class="flex w-full items-center justify-center whitespace-nowrap rounded-full border-2 border-black bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-gray-900"
|
49 |
+
on:click={(e) => {
|
50 |
+
if (!cookiesAreEnabled()) {
|
51 |
+
e.preventDefault();
|
52 |
+
window.open(window.location.href, "_blank");
|
53 |
+
}
|
54 |
+
|
55 |
+
$settings.ethicsModalAccepted = true;
|
56 |
+
}}
|
57 |
>
|
58 |
Start chatting
|
59 |
</button>
|
@@ -1,153 +0,0 @@
|
|
1 |
-
<script lang="ts">
|
2 |
-
import { createEventDispatcher } from "svelte";
|
3 |
-
|
4 |
-
import Modal from "$lib/components/Modal.svelte";
|
5 |
-
import CarbonClose from "~icons/carbon/close";
|
6 |
-
import CarbonCheckmark from "~icons/carbon/checkmark-filled";
|
7 |
-
import ModelCardMetadata from "./ModelCardMetadata.svelte";
|
8 |
-
import type { Model } from "$lib/types/Model";
|
9 |
-
import type { LayoutData } from "../../routes/$types";
|
10 |
-
import { enhance } from "$app/forms";
|
11 |
-
import { base } from "$app/paths";
|
12 |
-
|
13 |
-
import CarbonEdit from "~icons/carbon/edit";
|
14 |
-
import CarbonSave from "~icons/carbon/save";
|
15 |
-
import CarbonRestart from "~icons/carbon/restart";
|
16 |
-
|
17 |
-
export let settings: LayoutData["settings"];
|
18 |
-
export let models: Array<Model>;
|
19 |
-
|
20 |
-
let selectedModelId = settings.activeModel;
|
21 |
-
|
22 |
-
const dispatch = createEventDispatcher<{ close: void }>();
|
23 |
-
|
24 |
-
let expanded = false;
|
25 |
-
|
26 |
-
function onToggle() {
|
27 |
-
if (expanded) {
|
28 |
-
settings.customPrompts[selectedModelId] = value;
|
29 |
-
}
|
30 |
-
expanded = !expanded;
|
31 |
-
}
|
32 |
-
|
33 |
-
let value = "";
|
34 |
-
|
35 |
-
function onModelChange() {
|
36 |
-
value =
|
37 |
-
settings.customPrompts[selectedModelId] ??
|
38 |
-
models.filter((el) => el.id === selectedModelId)[0].preprompt ??
|
39 |
-
"";
|
40 |
-
}
|
41 |
-
|
42 |
-
$: selectedModelId, onModelChange();
|
43 |
-
</script>
|
44 |
-
|
45 |
-
<Modal width="max-w-lg" on:close>
|
46 |
-
<form
|
47 |
-
action="{base}/settings"
|
48 |
-
method="post"
|
49 |
-
on:submit={() => {
|
50 |
-
if (expanded) {
|
51 |
-
onToggle();
|
52 |
-
}
|
53 |
-
}}
|
54 |
-
use:enhance={() => {
|
55 |
-
dispatch("close");
|
56 |
-
}}
|
57 |
-
class="flex w-full flex-col gap-5 p-6"
|
58 |
-
>
|
59 |
-
{#each Object.entries(settings).filter(([k]) => !(k == "activeModel" || k === "customPrompts")) as [key, val]}
|
60 |
-
<input type="hidden" name={key} value={val} />
|
61 |
-
{/each}
|
62 |
-
<input type="hidden" name="customPrompts" value={JSON.stringify(settings.customPrompts)} />
|
63 |
-
<div class="flex items-start justify-between text-xl font-semibold text-gray-800">
|
64 |
-
<h2>Models</h2>
|
65 |
-
<button type="button" class="group" on:click={() => dispatch("close")}>
|
66 |
-
<CarbonClose class="text-gray-900 group-hover:text-gray-500" />
|
67 |
-
</button>
|
68 |
-
</div>
|
69 |
-
|
70 |
-
<div class="space-y-4">
|
71 |
-
{#each models as model}
|
72 |
-
{@const active = model.id === selectedModelId}
|
73 |
-
<div
|
74 |
-
class="relative rounded-xl border border-gray-100 {active
|
75 |
-
? 'bg-gradient-to-r from-primary-200/40 via-primary-500/10'
|
76 |
-
: ''}"
|
77 |
-
>
|
78 |
-
<label
|
79 |
-
class="group flex cursor-pointer flex-col p-3"
|
80 |
-
on:change
|
81 |
-
aria-label={model.displayName}
|
82 |
-
>
|
83 |
-
<input
|
84 |
-
type="radio"
|
85 |
-
class="sr-only"
|
86 |
-
name="activeModel"
|
87 |
-
value={model.id}
|
88 |
-
bind:group={selectedModelId}
|
89 |
-
/>
|
90 |
-
<div
|
91 |
-
class="mb-1.5 block pr-8 text-sm font-semibold leading-tight text-gray-800 sm:text-base"
|
92 |
-
>
|
93 |
-
{model.displayName}
|
94 |
-
</div>
|
95 |
-
{#if model.description}
|
96 |
-
<div class="text-xs text-gray-500 sm:text-sm">{model.description}</div>
|
97 |
-
{/if}
|
98 |
-
<CarbonCheckmark
|
99 |
-
class="absolute right-2 top-2 text-xl {active
|
100 |
-
? 'text-primary-400'
|
101 |
-
: 'text-transparent group-hover:text-gray-200'}"
|
102 |
-
/>
|
103 |
-
</label>
|
104 |
-
{#if active}
|
105 |
-
<div class=" overflow-hidden rounded-xl px-3 pb-2">
|
106 |
-
<div class="flex flex-row flex-nowrap gap-2 pb-1">
|
107 |
-
<div class="text-xs font-semibold text-gray-500">System Prompt</div>
|
108 |
-
{#if expanded}
|
109 |
-
<button
|
110 |
-
class="text-gray-500 hover:text-gray-900"
|
111 |
-
on:click|preventDefault={onToggle}
|
112 |
-
>
|
113 |
-
<CarbonSave class="text-sm" />
|
114 |
-
</button>
|
115 |
-
<button
|
116 |
-
class="text-gray-500 hover:text-gray-900"
|
117 |
-
on:click|preventDefault={() => {
|
118 |
-
value = model.preprompt ?? "";
|
119 |
-
}}
|
120 |
-
>
|
121 |
-
<CarbonRestart class="text-sm" />
|
122 |
-
</button>
|
123 |
-
{:else}
|
124 |
-
<button
|
125 |
-
class=" text-gray-500 hover:text-gray-900"
|
126 |
-
on:click|preventDefault={onToggle}
|
127 |
-
>
|
128 |
-
<CarbonEdit class="text-sm" />
|
129 |
-
</button>
|
130 |
-
{/if}
|
131 |
-
</div>
|
132 |
-
<textarea
|
133 |
-
enterkeyhint="send"
|
134 |
-
tabindex="0"
|
135 |
-
rows="1"
|
136 |
-
class="h-20 w-full resize-none scroll-p-3 overflow-x-hidden overflow-y-scroll rounded-md border border-gray-300 bg-transparent p-1 text-xs outline-none focus:ring-0 focus-visible:ring-0"
|
137 |
-
bind:value
|
138 |
-
hidden={!expanded}
|
139 |
-
/>
|
140 |
-
</div>
|
141 |
-
{/if}
|
142 |
-
<ModelCardMetadata {model} />
|
143 |
-
</div>
|
144 |
-
{/each}
|
145 |
-
</div>
|
146 |
-
<button
|
147 |
-
type="submit"
|
148 |
-
class="sticky bottom-6 mt-2 rounded-full bg-black px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-colors hover:ring"
|
149 |
-
>
|
150 |
-
Apply
|
151 |
-
</button>
|
152 |
-
</form>
|
153 |
-
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,6 +1,5 @@
|
|
1 |
<script lang="ts">
|
2 |
import { base } from "$app/paths";
|
3 |
-
import { createEventDispatcher } from "svelte";
|
4 |
|
5 |
import Logo from "$lib/components/icons/Logo.svelte";
|
6 |
import { switchTheme } from "$lib/switchTheme";
|
@@ -9,12 +8,6 @@
|
|
9 |
import NavConversationItem from "./NavConversationItem.svelte";
|
10 |
import type { LayoutData } from "../../routes/$types";
|
11 |
|
12 |
-
const dispatch = createEventDispatcher<{
|
13 |
-
shareConversation: { id: string; title: string };
|
14 |
-
clickSettings: void;
|
15 |
-
clickLogout: void;
|
16 |
-
}>();
|
17 |
-
|
18 |
interface Conv {
|
19 |
id: string;
|
20 |
title: string;
|
@@ -119,13 +112,12 @@
|
|
119 |
>
|
120 |
Theme
|
121 |
</button>
|
122 |
-
<
|
123 |
-
|
124 |
-
type="button"
|
125 |
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
126 |
>
|
127 |
Settings
|
128 |
-
</
|
129 |
{#if PUBLIC_APP_NAME === "HuggingChat"}
|
130 |
<a
|
131 |
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
|
|
|
1 |
<script lang="ts">
|
2 |
import { base } from "$app/paths";
|
|
|
3 |
|
4 |
import Logo from "$lib/components/icons/Logo.svelte";
|
5 |
import { switchTheme } from "$lib/switchTheme";
|
|
|
8 |
import NavConversationItem from "./NavConversationItem.svelte";
|
9 |
import type { LayoutData } from "../../routes/$types";
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
interface Conv {
|
12 |
id: string;
|
13 |
title: string;
|
|
|
112 |
>
|
113 |
Theme
|
114 |
</button>
|
115 |
+
<a
|
116 |
+
href="{base}/settings"
|
|
|
117 |
class="flex h-9 flex-none items-center gap-1.5 rounded-lg pl-2.5 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
118 |
>
|
119 |
Settings
|
120 |
+
</a>
|
121 |
{#if PUBLIC_APP_NAME === "HuggingChat"}
|
122 |
<a
|
123 |
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
|
@@ -1,132 +0,0 @@
|
|
1 |
-
<script lang="ts">
|
2 |
-
import { createEventDispatcher } from "svelte";
|
3 |
-
|
4 |
-
import Modal from "$lib/components/Modal.svelte";
|
5 |
-
import CarbonClose from "~icons/carbon/close";
|
6 |
-
import Switch from "$lib/components/Switch.svelte";
|
7 |
-
import { enhance } from "$app/forms";
|
8 |
-
import { base } from "$app/paths";
|
9 |
-
import { PUBLIC_APP_DATA_SHARING } from "$env/static/public";
|
10 |
-
import type { Model } from "$lib/types/Model";
|
11 |
-
import type { LayoutData } from "../../routes/$types";
|
12 |
-
|
13 |
-
export let settings: LayoutData["settings"];
|
14 |
-
export let models: Array<Model>;
|
15 |
-
|
16 |
-
let shareConversationsWithModelAuthors = settings.shareConversationsWithModelAuthors;
|
17 |
-
let isConfirmingDeletion = false;
|
18 |
-
|
19 |
-
const dispatch = createEventDispatcher<{ close: void }>();
|
20 |
-
</script>
|
21 |
-
|
22 |
-
<Modal on:close>
|
23 |
-
<div class="flex w-full flex-col gap-5 p-6">
|
24 |
-
<div class="flex items-start justify-between text-xl font-semibold text-gray-800">
|
25 |
-
<h2>Settings</h2>
|
26 |
-
<button type="button" class="group" on:click={() => dispatch("close")}>
|
27 |
-
<CarbonClose class="text-gray-900 group-hover:text-gray-500" />
|
28 |
-
</button>
|
29 |
-
</div>
|
30 |
-
<form
|
31 |
-
class="flex flex-col gap-5"
|
32 |
-
use:enhance={() => {
|
33 |
-
dispatch("close");
|
34 |
-
}}
|
35 |
-
method="post"
|
36 |
-
action="{base}/settings"
|
37 |
-
>
|
38 |
-
{#if PUBLIC_APP_DATA_SHARING}
|
39 |
-
<label class="flex cursor-pointer select-none items-center gap-2 text-gray-500">
|
40 |
-
{#each Object.entries(settings).filter(([k]) => !(k === "shareConversationsWithModelAuthors" || k === "customPrompts")) as [key, val]}
|
41 |
-
<input type="hidden" name={key} value={val} />
|
42 |
-
{/each}
|
43 |
-
<input
|
44 |
-
type="hidden"
|
45 |
-
name="customPrompts"
|
46 |
-
value={JSON.stringify(settings.customPrompts)}
|
47 |
-
/>
|
48 |
-
<Switch
|
49 |
-
name="shareConversationsWithModelAuthors"
|
50 |
-
bind:checked={shareConversationsWithModelAuthors}
|
51 |
-
/>
|
52 |
-
Share conversations with model authors
|
53 |
-
</label>
|
54 |
-
|
55 |
-
<p class="text-gray-800">
|
56 |
-
Sharing your data will help improve the training data and make open models better over
|
57 |
-
time.
|
58 |
-
</p>
|
59 |
-
<p class="text-gray-800">
|
60 |
-
You can change this setting at any time, it applies to all your conversations.
|
61 |
-
</p>
|
62 |
-
<div>
|
63 |
-
<p class="text-gray-800">Read more about model authors:</p>
|
64 |
-
<ul class="list-inside list-disc">
|
65 |
-
{#each models as model}
|
66 |
-
<li class="list-item">
|
67 |
-
<a
|
68 |
-
href={model["websiteUrl"]}
|
69 |
-
target="_blank"
|
70 |
-
rel="noreferrer"
|
71 |
-
class="underline decoration-gray-300 hover:decoration-gray-700">{model["name"]}</a
|
72 |
-
>
|
73 |
-
</li>
|
74 |
-
{/each}
|
75 |
-
</ul>
|
76 |
-
</div>
|
77 |
-
{/if}
|
78 |
-
<label class="flex cursor-pointer select-none items-center gap-2 text-sm text-gray-500">
|
79 |
-
<input
|
80 |
-
type="checkbox"
|
81 |
-
name="hideEmojiOnSidebar"
|
82 |
-
bind:checked={settings.hideEmojiOnSidebar}
|
83 |
-
/>
|
84 |
-
Hide emoticons in conversation topics
|
85 |
-
</label>
|
86 |
-
<form
|
87 |
-
method="post"
|
88 |
-
action="{base}/conversations?/delete"
|
89 |
-
on:submit|preventDefault={() => (isConfirmingDeletion = true)}
|
90 |
-
>
|
91 |
-
<button type="submit" class="underline decoration-gray-300 hover:decoration-gray-700">
|
92 |
-
Delete all conversations
|
93 |
-
</button>
|
94 |
-
</form>
|
95 |
-
<button
|
96 |
-
type="submit"
|
97 |
-
class="mt-2 rounded-full bg-black px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-all focus-visible:outline-none focus-visible:ring hover:ring"
|
98 |
-
>
|
99 |
-
Apply
|
100 |
-
</button>
|
101 |
-
</form>
|
102 |
-
|
103 |
-
{#if isConfirmingDeletion}
|
104 |
-
<Modal on:close={() => (isConfirmingDeletion = false)}>
|
105 |
-
<form
|
106 |
-
use:enhance={() => {
|
107 |
-
dispatch("close");
|
108 |
-
}}
|
109 |
-
method="post"
|
110 |
-
action="{base}/conversations?/delete"
|
111 |
-
class="flex w-full flex-col gap-5 p-6"
|
112 |
-
>
|
113 |
-
<div class="flex items-start justify-between text-xl font-semibold text-gray-800">
|
114 |
-
<h2>Are you sure?</h2>
|
115 |
-
<button type="button" class="group" on:click={() => (isConfirmingDeletion = false)}>
|
116 |
-
<CarbonClose class="text-gray-900 group-hover:text-gray-500" />
|
117 |
-
</button>
|
118 |
-
</div>
|
119 |
-
<p class="text-gray-800">
|
120 |
-
This action will delete all your conversations. This cannot be undone.
|
121 |
-
</p>
|
122 |
-
<button
|
123 |
-
type="submit"
|
124 |
-
class="mt-2 rounded-full bg-red-700 px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-all focus-visible:outline-none focus-visible:ring hover:ring"
|
125 |
-
>
|
126 |
-
Confirm deletion
|
127 |
-
</button>
|
128 |
-
</form>
|
129 |
-
</Modal>
|
130 |
-
{/if}
|
131 |
-
</div>
|
132 |
-
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -10,9 +10,7 @@
|
|
10 |
aria-label="switch"
|
11 |
role="switch"
|
12 |
tabindex="0"
|
13 |
-
|
14 |
-
on:keypress
|
15 |
-
class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full bg-gray-300 p-1 shadow-inner ring-gray-400 transition-all peer-checked:bg-blue-600 peer-focus-visible:ring peer-focus-visible:ring-offset-1 hover:bg-gray-400 dark:bg-gray-600 peer-checked:[&>div]:translate-x-3.5"
|
16 |
>
|
17 |
<div class="h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-all" />
|
18 |
</div>
|
|
|
10 |
aria-label="switch"
|
11 |
role="switch"
|
12 |
tabindex="0"
|
13 |
+
class="relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full bg-gray-300 p-1 shadow-inner ring-gray-400 transition-all peer-checked:bg-blue-600 peer-focus-visible:ring peer-focus-visible:ring-offset-1 hover:bg-gray-400 dark:bg-gray-600 peer-checked:[&>div]:translate-x-3.5"
|
|
|
|
|
14 |
>
|
15 |
<div class="h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-all" />
|
16 |
</div>
|
@@ -4,21 +4,19 @@
|
|
4 |
import { PUBLIC_APP_DESCRIPTION } from "$env/static/public";
|
5 |
import Logo from "$lib/components/icons/Logo.svelte";
|
6 |
import { createEventDispatcher } from "svelte";
|
7 |
-
import
|
8 |
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
|
9 |
import AnnouncementBanner from "../AnnouncementBanner.svelte";
|
10 |
-
import ModelsModal from "../ModelsModal.svelte";
|
11 |
import type { Model } from "$lib/types/Model";
|
12 |
import ModelCardMetadata from "../ModelCardMetadata.svelte";
|
13 |
import type { LayoutData } from "../../../routes/$types";
|
14 |
import { findCurrentModel } from "$lib/utils/models";
|
|
|
15 |
|
16 |
export let currentModel: Model;
|
17 |
export let settings: LayoutData["settings"];
|
18 |
export let models: Model[];
|
19 |
|
20 |
-
let isModelsModalOpen = false;
|
21 |
-
|
22 |
$: currentModelMetadata = findCurrentModel(models, settings.activeModel);
|
23 |
|
24 |
const announcementBanners = PUBLIC_ANNOUNCEMENT_BANNERS
|
@@ -57,21 +55,16 @@
|
|
57 |
>
|
58 |
</AnnouncementBanner>
|
59 |
{/each}
|
60 |
-
|
61 |
-
{#if isModelsModalOpen}
|
62 |
-
<ModelsModal {settings} {models} on:close={() => (isModelsModalOpen = false)} />
|
63 |
-
{/if}
|
64 |
<div class="overflow-hidden rounded-xl border dark:border-gray-800">
|
65 |
<div class="flex p-3">
|
66 |
<div>
|
67 |
<div class="text-sm text-gray-600 dark:text-gray-400">Current Model</div>
|
68 |
<div class="font-semibold">{currentModel.displayName}</div>
|
69 |
</div>
|
70 |
-
<
|
71 |
-
|
72 |
-
on:click={() => (isModelsModalOpen = true)}
|
73 |
class="btn ml-auto flex h-7 w-7 self-start rounded-full bg-gray-100 p-1 text-xs hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:hover:bg-gray-600"
|
74 |
-
><
|
75 |
>
|
76 |
</div>
|
77 |
<ModelCardMetadata variant="dark" model={currentModel} />
|
|
|
4 |
import { PUBLIC_APP_DESCRIPTION } from "$env/static/public";
|
5 |
import Logo from "$lib/components/icons/Logo.svelte";
|
6 |
import { createEventDispatcher } from "svelte";
|
7 |
+
import IconGear from "~icons/bi/gear-fill";
|
8 |
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
|
9 |
import AnnouncementBanner from "../AnnouncementBanner.svelte";
|
|
|
10 |
import type { Model } from "$lib/types/Model";
|
11 |
import ModelCardMetadata from "../ModelCardMetadata.svelte";
|
12 |
import type { LayoutData } from "../../../routes/$types";
|
13 |
import { findCurrentModel } from "$lib/utils/models";
|
14 |
+
import { base } from "$app/paths";
|
15 |
|
16 |
export let currentModel: Model;
|
17 |
export let settings: LayoutData["settings"];
|
18 |
export let models: Model[];
|
19 |
|
|
|
|
|
20 |
$: currentModelMetadata = findCurrentModel(models, settings.activeModel);
|
21 |
|
22 |
const announcementBanners = PUBLIC_ANNOUNCEMENT_BANNERS
|
|
|
55 |
>
|
56 |
</AnnouncementBanner>
|
57 |
{/each}
|
|
|
|
|
|
|
|
|
58 |
<div class="overflow-hidden rounded-xl border dark:border-gray-800">
|
59 |
<div class="flex p-3">
|
60 |
<div>
|
61 |
<div class="text-sm text-gray-600 dark:text-gray-400">Current Model</div>
|
62 |
<div class="font-semibold">{currentModel.displayName}</div>
|
63 |
</div>
|
64 |
+
<a
|
65 |
+
href="{base}/settings/{currentModel.id}"
|
|
|
66 |
class="btn ml-auto flex h-7 w-7 self-start rounded-full bg-gray-100 p-1 text-xs hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:hover:bg-gray-600"
|
67 |
+
><IconGear /></a
|
68 |
>
|
69 |
</div>
|
70 |
<ModelCardMetadata variant="dark" model={currentModel} />
|
@@ -13,7 +13,6 @@
|
|
13 |
import ChatInput from "./ChatInput.svelte";
|
14 |
import StopGeneratingBtn from "../StopGeneratingBtn.svelte";
|
15 |
import type { Model } from "$lib/types/Model";
|
16 |
-
import type { LayoutData } from "../../../routes/$types";
|
17 |
import WebSearchToggle from "../WebSearchToggle.svelte";
|
18 |
import LoginModal from "../LoginModal.svelte";
|
19 |
import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
|
@@ -23,6 +22,7 @@
|
|
23 |
import RetryBtn from "../RetryBtn.svelte";
|
24 |
import UploadBtn from "../UploadBtn.svelte";
|
25 |
import file2base64 from "$lib/utils/file2base64";
|
|
|
26 |
|
27 |
export let messages: Message[] = [];
|
28 |
export let loading = false;
|
@@ -30,7 +30,6 @@
|
|
30 |
export let shared = false;
|
31 |
export let currentModel: Model;
|
32 |
export let models: Model[];
|
33 |
-
export let settings: LayoutData["settings"];
|
34 |
export let webSearchMessages: WebSearchUpdate[] = [];
|
35 |
export let preprompt: string | undefined = undefined;
|
36 |
export let files: File[] = [];
|
@@ -72,14 +71,15 @@
|
|
72 |
$: lastIsError = messages[messages.length - 1]?.from === "user" && !loading;
|
73 |
|
74 |
$: sources = files.map((file) => file2base64(file));
|
|
|
|
|
75 |
</script>
|
76 |
|
77 |
<div class="relative min-h-0 min-w-0">
|
78 |
-
{#if
|
79 |
-
<DisclaimerModal
|
80 |
{:else if loginModalOpen}
|
81 |
<LoginModal
|
82 |
-
{settings}
|
83 |
on:close={() => {
|
84 |
loginModalOpen = false;
|
85 |
}}
|
@@ -88,7 +88,7 @@
|
|
88 |
<ChatMessages
|
89 |
{loading}
|
90 |
{pending}
|
91 |
-
{settings}
|
92 |
{currentModel}
|
93 |
{models}
|
94 |
{messages}
|
@@ -139,7 +139,7 @@
|
|
139 |
class="dark:via-gray-80 w-full bg-gradient-to-t from-white via-white/80 to-white/0 dark:border-gray-800 dark:from-gray-900 dark:to-gray-900/0 max-md:border-t max-md:bg-white max-md:px-4 max-md:dark:bg-gray-900"
|
140 |
>
|
141 |
<div class="flex w-full pb-3 max-md:pt-3">
|
142 |
-
{#if settings?.searchEnabled}
|
143 |
<WebSearchToggle />
|
144 |
{/if}
|
145 |
{#if loading}
|
|
|
13 |
import ChatInput from "./ChatInput.svelte";
|
14 |
import StopGeneratingBtn from "../StopGeneratingBtn.svelte";
|
15 |
import type { Model } from "$lib/types/Model";
|
|
|
16 |
import WebSearchToggle from "../WebSearchToggle.svelte";
|
17 |
import LoginModal from "../LoginModal.svelte";
|
18 |
import type { WebSearchUpdate } from "$lib/types/MessageUpdate";
|
|
|
22 |
import RetryBtn from "../RetryBtn.svelte";
|
23 |
import UploadBtn from "../UploadBtn.svelte";
|
24 |
import file2base64 from "$lib/utils/file2base64";
|
25 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
26 |
|
27 |
export let messages: Message[] = [];
|
28 |
export let loading = false;
|
|
|
30 |
export let shared = false;
|
31 |
export let currentModel: Model;
|
32 |
export let models: Model[];
|
|
|
33 |
export let webSearchMessages: WebSearchUpdate[] = [];
|
34 |
export let preprompt: string | undefined = undefined;
|
35 |
export let files: File[] = [];
|
|
|
71 |
$: lastIsError = messages[messages.length - 1]?.from === "user" && !loading;
|
72 |
|
73 |
$: sources = files.map((file) => file2base64(file));
|
74 |
+
|
75 |
+
const settings = useSettingsStore();
|
76 |
</script>
|
77 |
|
78 |
<div class="relative min-h-0 min-w-0">
|
79 |
+
{#if !$settings.ethicsModalAccepted}
|
80 |
+
<DisclaimerModal />
|
81 |
{:else if loginModalOpen}
|
82 |
<LoginModal
|
|
|
83 |
on:close={() => {
|
84 |
loginModalOpen = false;
|
85 |
}}
|
|
|
88 |
<ChatMessages
|
89 |
{loading}
|
90 |
{pending}
|
91 |
+
settings={$page.data.settings}
|
92 |
{currentModel}
|
93 |
{models}
|
94 |
{messages}
|
|
|
139 |
class="dark:via-gray-80 w-full bg-gradient-to-t from-white via-white/80 to-white/0 dark:border-gray-800 dark:from-gray-900 dark:to-gray-900/0 max-md:border-t max-md:bg-white max-md:px-4 max-md:dark:bg-gray-900"
|
140 |
>
|
141 |
<div class="flex w-full pb-3 max-md:pt-3">
|
142 |
+
{#if $page.data.settings?.searchEnabled}
|
143 |
<WebSearchToggle />
|
144 |
{/if}
|
145 |
{#if loading}
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { browser } from "$app/environment";
|
2 |
+
import { base } from "$app/paths";
|
3 |
+
import { getContext, setContext } from "svelte";
|
4 |
+
import { type Writable, writable, get } from "svelte/store";
|
5 |
+
|
6 |
+
type SettingsStore = {
|
7 |
+
shareConversationsWithModelAuthors: boolean;
|
8 |
+
hideEmojiOnSidebar: boolean;
|
9 |
+
ethicsModalAccepted: boolean;
|
10 |
+
ethicsModalAcceptedAt: Date | null;
|
11 |
+
activeModel: string;
|
12 |
+
customPrompts: Record<string, string>;
|
13 |
+
recentlySaved: boolean;
|
14 |
+
};
|
15 |
+
export function useSettingsStore() {
|
16 |
+
return getContext<Writable<SettingsStore>>("settings");
|
17 |
+
}
|
18 |
+
|
19 |
+
export function createSettingsStore(initialValue: Omit<SettingsStore, "recentlySaved">) {
|
20 |
+
const baseStore = writable({ ...initialValue, recentlySaved: false });
|
21 |
+
|
22 |
+
let timeoutId: NodeJS.Timeout;
|
23 |
+
|
24 |
+
async function setSettings(settings: Partial<SettingsStore>) {
|
25 |
+
baseStore.update((s) => ({
|
26 |
+
...s,
|
27 |
+
...settings,
|
28 |
+
}));
|
29 |
+
|
30 |
+
clearTimeout(timeoutId);
|
31 |
+
|
32 |
+
if (browser) {
|
33 |
+
timeoutId = setTimeout(async () => {
|
34 |
+
await fetch(`${base}/settings`, {
|
35 |
+
method: "POST",
|
36 |
+
headers: {
|
37 |
+
"Content-Type": "application/json",
|
38 |
+
},
|
39 |
+
body: JSON.stringify({
|
40 |
+
...get(baseStore),
|
41 |
+
...settings,
|
42 |
+
}),
|
43 |
+
});
|
44 |
+
|
45 |
+
// set savedRecently to true for 3s
|
46 |
+
baseStore.update((s) => ({
|
47 |
+
...s,
|
48 |
+
recentlySaved: true,
|
49 |
+
}));
|
50 |
+
setTimeout(() => {
|
51 |
+
baseStore.update((s) => ({
|
52 |
+
...s,
|
53 |
+
recentlySaved: false,
|
54 |
+
}));
|
55 |
+
}, 3000);
|
56 |
+
}, 300);
|
57 |
+
// debounce server calls by 300ms
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
const newStore = {
|
62 |
+
subscribe: baseStore.subscribe,
|
63 |
+
set: setSettings,
|
64 |
+
update: (fn: (s: SettingsStore) => SettingsStore) => {
|
65 |
+
setSettings(fn(get(baseStore)));
|
66 |
+
},
|
67 |
+
} satisfies Writable<SettingsStore>;
|
68 |
+
|
69 |
+
setContext("settings", newStore);
|
70 |
+
|
71 |
+
return newStore;
|
72 |
+
}
|
@@ -24,4 +24,5 @@ export interface Settings extends Timestamps {
|
|
24 |
export const DEFAULT_SETTINGS = {
|
25 |
shareConversationsWithModelAuthors: true,
|
26 |
activeModel: defaultModel.id,
|
|
|
27 |
};
|
|
|
24 |
export const DEFAULT_SETTINGS = {
|
25 |
shareConversationsWithModelAuthors: true,
|
26 |
activeModel: defaultModel.id,
|
27 |
+
hideEmojiOnSidebar: false,
|
28 |
};
|
@@ -83,13 +83,14 @@ export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
|
|
83 |
}))
|
84 |
.toArray(),
|
85 |
settings: {
|
86 |
-
|
87 |
-
|
88 |
-
DEFAULT_SETTINGS.shareConversationsWithModelAuthors,
|
89 |
ethicsModalAcceptedAt: settings?.ethicsModalAcceptedAt ?? null,
|
90 |
activeModel: settings?.activeModel ?? DEFAULT_SETTINGS.activeModel,
|
91 |
hideEmojiOnSidebar: settings?.hideEmojiOnSidebar ?? false,
|
92 |
-
|
|
|
|
|
93 |
customPrompts: settings?.customPrompts ?? {},
|
94 |
},
|
95 |
models: models.map((model) => ({
|
|
|
83 |
}))
|
84 |
.toArray(),
|
85 |
settings: {
|
86 |
+
searchEnabled: !!(SERPAPI_KEY || SERPER_API_KEY || YDC_API_KEY || USE_LOCAL_WEBSEARCH),
|
87 |
+
ethicsModalAccepted: !!settings?.ethicsModalAcceptedAt,
|
|
|
88 |
ethicsModalAcceptedAt: settings?.ethicsModalAcceptedAt ?? null,
|
89 |
activeModel: settings?.activeModel ?? DEFAULT_SETTINGS.activeModel,
|
90 |
hideEmojiOnSidebar: settings?.hideEmojiOnSidebar ?? false,
|
91 |
+
shareConversationsWithModelAuthors:
|
92 |
+
settings?.shareConversationsWithModelAuthors ??
|
93 |
+
DEFAULT_SETTINGS.shareConversationsWithModelAuthors,
|
94 |
customPrompts: settings?.customPrompts ?? {},
|
95 |
},
|
96 |
models: models.map((model) => ({
|
@@ -13,14 +13,13 @@
|
|
13 |
import MobileNav from "$lib/components/MobileNav.svelte";
|
14 |
import NavMenu from "$lib/components/NavMenu.svelte";
|
15 |
import Toast from "$lib/components/Toast.svelte";
|
16 |
-
import SettingsModal from "$lib/components/SettingsModal.svelte";
|
17 |
import { PUBLIC_APP_ASSETS, PUBLIC_APP_NAME } from "$env/static/public";
|
18 |
import titleUpdate from "$lib/stores/titleUpdate";
|
|
|
19 |
|
20 |
export let data;
|
21 |
|
22 |
let isNavOpen = false;
|
23 |
-
let isSettingsOpen = false;
|
24 |
let errorToastTimeout: ReturnType<typeof setTimeout>;
|
25 |
let currentError: string | null;
|
26 |
|
@@ -104,6 +103,8 @@
|
|
104 |
|
105 |
$titleUpdate = null;
|
106 |
}
|
|
|
|
|
107 |
</script>
|
108 |
|
109 |
<svelte:head>
|
@@ -152,7 +153,6 @@
|
|
152 |
canLogin={data.user === undefined && data.loginEnabled}
|
153 |
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
154 |
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
155 |
-
on:clickSettings={() => (isSettingsOpen = true)}
|
156 |
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
|
157 |
/>
|
158 |
</MobileNav>
|
@@ -163,19 +163,11 @@
|
|
163 |
canLogin={data.user === undefined && data.loginEnabled}
|
164 |
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
165 |
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
166 |
-
on:clickSettings={() => (isSettingsOpen = true)}
|
167 |
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
|
168 |
/>
|
169 |
</nav>
|
170 |
{#if currentError}
|
171 |
<Toast message={currentError} />
|
172 |
{/if}
|
173 |
-
{#if isSettingsOpen}
|
174 |
-
<SettingsModal
|
175 |
-
on:close={() => (isSettingsOpen = false)}
|
176 |
-
settings={data.settings}
|
177 |
-
models={data.models}
|
178 |
-
/>
|
179 |
-
{/if}
|
180 |
<slot />
|
181 |
</div>
|
|
|
13 |
import MobileNav from "$lib/components/MobileNav.svelte";
|
14 |
import NavMenu from "$lib/components/NavMenu.svelte";
|
15 |
import Toast from "$lib/components/Toast.svelte";
|
|
|
16 |
import { PUBLIC_APP_ASSETS, PUBLIC_APP_NAME } from "$env/static/public";
|
17 |
import titleUpdate from "$lib/stores/titleUpdate";
|
18 |
+
import { createSettingsStore } from "$lib/stores/settings";
|
19 |
|
20 |
export let data;
|
21 |
|
22 |
let isNavOpen = false;
|
|
|
23 |
let errorToastTimeout: ReturnType<typeof setTimeout>;
|
24 |
let currentError: string | null;
|
25 |
|
|
|
103 |
|
104 |
$titleUpdate = null;
|
105 |
}
|
106 |
+
|
107 |
+
createSettingsStore(data.settings);
|
108 |
</script>
|
109 |
|
110 |
<svelte:head>
|
|
|
153 |
canLogin={data.user === undefined && data.loginEnabled}
|
154 |
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
155 |
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
|
|
156 |
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
|
157 |
/>
|
158 |
</MobileNav>
|
|
|
163 |
canLogin={data.user === undefined && data.loginEnabled}
|
164 |
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
165 |
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
|
|
166 |
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
|
167 |
/>
|
168 |
</nav>
|
169 |
{#if currentError}
|
170 |
<Toast message={currentError} />
|
171 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
<slot />
|
173 |
</div>
|
@@ -59,6 +59,5 @@
|
|
59 |
{loading}
|
60 |
currentModel={findCurrentModel([...data.models, ...data.oldModels], data.settings.activeModel)}
|
61 |
models={data.models}
|
62 |
-
settings={data.settings}
|
63 |
bind:files
|
64 |
/>
|
|
|
59 |
{loading}
|
60 |
currentModel={findCurrentModel([...data.models, ...data.oldModels], data.settings.activeModel)}
|
61 |
models={data.models}
|
|
|
62 |
bind:files
|
63 |
/>
|
@@ -15,7 +15,7 @@
|
|
15 |
import type { Message } from "$lib/types/Message";
|
16 |
import type { MessageUpdate, WebSearchUpdate } from "$lib/types/MessageUpdate";
|
17 |
import titleUpdate from "$lib/stores/titleUpdate";
|
18 |
-
import file2base64 from "$lib/utils/file2base64
|
19 |
export let data;
|
20 |
|
21 |
let messages = data.messages;
|
@@ -336,5 +336,4 @@
|
|
336 |
on:stop={() => (($isAborted = true), (loading = false))}
|
337 |
models={data.models}
|
338 |
currentModel={findCurrentModel([...data.models, ...data.oldModels], data.model)}
|
339 |
-
settings={data.settings}
|
340 |
/>
|
|
|
15 |
import type { Message } from "$lib/types/Message";
|
16 |
import type { MessageUpdate, WebSearchUpdate } from "$lib/types/MessageUpdate";
|
17 |
import titleUpdate from "$lib/stores/titleUpdate";
|
18 |
+
import file2base64 from "$lib/utils/file2base64";
|
19 |
export let data;
|
20 |
|
21 |
let messages = data.messages;
|
|
|
336 |
on:stop={() => (($isAborted = true), (loading = false))}
|
337 |
models={data.models}
|
338 |
currentModel={findCurrentModel([...data.models, ...data.oldModels], data.model)}
|
|
|
339 |
/>
|
@@ -12,7 +12,7 @@ import { runWebSearch } from "$lib/server/websearch/runWebSearch";
|
|
12 |
import type { WebSearch } from "$lib/types/WebSearch";
|
13 |
import { abortedGenerations } from "$lib/server/abortedGenerations";
|
14 |
import { summarize } from "$lib/server/summarize";
|
15 |
-
import { uploadFile } from "$lib/server/files/uploadFile
|
16 |
import sizeof from "image-size";
|
17 |
|
18 |
export async function POST({ request, locals, params, getClientAddress }) {
|
@@ -302,7 +302,6 @@ export async function POST({ request, locals, params, getClientAddress }) {
|
|
302 |
}
|
303 |
}
|
304 |
} catch (e) {
|
305 |
-
console.error(e);
|
306 |
update({ type: "status", status: "error", message: (e as Error).message });
|
307 |
}
|
308 |
await collections.conversations.updateOne(
|
|
|
12 |
import type { WebSearch } from "$lib/types/WebSearch";
|
13 |
import { abortedGenerations } from "$lib/server/abortedGenerations";
|
14 |
import { summarize } from "$lib/server/summarize";
|
15 |
+
import { uploadFile } from "$lib/server/files/uploadFile";
|
16 |
import sizeof from "image-size";
|
17 |
|
18 |
export async function POST({ request, locals, params, getClientAddress }) {
|
|
|
302 |
}
|
303 |
}
|
304 |
} catch (e) {
|
|
|
305 |
update({ type: "status", status: "error", message: (e as Error).message });
|
306 |
}
|
307 |
await collections.conversations.updateOne(
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
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 |
+
<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="z-10 grid h-[95dvh] w-[90dvw] grid-cols-1 content-start gap-x-10 gap-y-6 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-[1100px]"
|
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 |
+
>
|
45 |
+
<CarbonClose class="text-xl text-gray-900 hover:text-black" />
|
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-[160px] max-md:border md:pr-6"
|
50 |
+
>
|
51 |
+
{#each data.models as model}
|
52 |
+
<a
|
53 |
+
href="{base}/settings/{model.id}"
|
54 |
+
class="group flex h-11 flex-none items-center gap-3 pl-3 pr-2 text-gray-500 hover:bg-gray-100 md:rounded-xl {model.id ===
|
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-11 flex-none items-center gap-3 pl-3 pr-2 text-gray-500 hover:bg-gray-100 max-md:order-first md:rounded-xl {$page
|
72 |
+
.params.model === undefined
|
73 |
+
? '!bg-gray-100 !text-gray-800'
|
74 |
+
: ''}"
|
75 |
+
>
|
76 |
+
<UserIcon class="pr-1 text-lg" />
|
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 |
+
|
84 |
+
{#if $settings.recentlySaved}
|
85 |
+
<div class="absolute bottom-0 right-0 m-2 inline p-2 text-gray-400">
|
86 |
+
<CarbonCheckmark class="inline text-lg" />
|
87 |
+
Saved
|
88 |
+
</div>
|
89 |
+
{/if}
|
90 |
+
</dialog>
|
91 |
+
</div>
|
@@ -1,53 +0,0 @@
|
|
1 |
-
import { base } from "$app/paths";
|
2 |
-
import { collections } from "$lib/server/database";
|
3 |
-
import { redirect } from "@sveltejs/kit";
|
4 |
-
import { z } from "zod";
|
5 |
-
import { models, validateModel } from "$lib/server/models";
|
6 |
-
import { authCondition } from "$lib/server/auth";
|
7 |
-
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
8 |
-
|
9 |
-
const booleanFormObject = z
|
10 |
-
.union([z.literal("true"), z.literal("on"), z.literal("false"), z.null()])
|
11 |
-
.transform((value) => {
|
12 |
-
return value === "true" || value === "on";
|
13 |
-
});
|
14 |
-
|
15 |
-
export const actions = {
|
16 |
-
default: async function ({ request, locals }) {
|
17 |
-
const formData = await request.formData();
|
18 |
-
|
19 |
-
const { ethicsModalAccepted, ...settings } = z
|
20 |
-
.object({
|
21 |
-
shareConversationsWithModelAuthors: booleanFormObject,
|
22 |
-
hideEmojiOnSidebar: booleanFormObject,
|
23 |
-
ethicsModalAccepted: z.boolean({ coerce: true }).optional(),
|
24 |
-
activeModel: validateModel(models),
|
25 |
-
customPrompts: z.record(z.string()).default({}),
|
26 |
-
})
|
27 |
-
.parse({
|
28 |
-
hideEmojiOnSidebar: formData.get("hideEmojiOnSidebar"),
|
29 |
-
shareConversationsWithModelAuthors: formData.get("shareConversationsWithModelAuthors"),
|
30 |
-
ethicsModalAccepted: formData.get("ethicsModalAccepted"),
|
31 |
-
activeModel: formData.get("activeModel") ?? DEFAULT_SETTINGS.activeModel,
|
32 |
-
customPrompts: JSON.parse(formData.get("customPrompts")?.toString() ?? "{}"),
|
33 |
-
});
|
34 |
-
|
35 |
-
await collections.settings.updateOne(
|
36 |
-
authCondition(locals),
|
37 |
-
{
|
38 |
-
$set: {
|
39 |
-
...settings,
|
40 |
-
...(ethicsModalAccepted && { ethicsModalAcceptedAt: new Date() }),
|
41 |
-
updatedAt: new Date(),
|
42 |
-
},
|
43 |
-
$setOnInsert: {
|
44 |
-
createdAt: new Date(),
|
45 |
-
},
|
46 |
-
},
|
47 |
-
{
|
48 |
-
upsert: true,
|
49 |
-
}
|
50 |
-
);
|
51 |
-
throw redirect(303, request.headers.get("referer") || `${base}/`);
|
52 |
-
},
|
53 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { createEventDispatcher } from "svelte";
|
3 |
+
|
4 |
+
import Modal from "$lib/components/Modal.svelte";
|
5 |
+
import CarbonClose from "~icons/carbon/close";
|
6 |
+
import CarbonTrashCan from "~icons/carbon/trash-can";
|
7 |
+
|
8 |
+
import { enhance } from "$app/forms";
|
9 |
+
import { base } from "$app/paths";
|
10 |
+
|
11 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
12 |
+
import Switch from "$lib/components/Switch.svelte";
|
13 |
+
|
14 |
+
let isConfirmingDeletion = false;
|
15 |
+
|
16 |
+
const dispatch = createEventDispatcher<{ close: void }>();
|
17 |
+
|
18 |
+
let settings = useSettingsStore();
|
19 |
+
</script>
|
20 |
+
|
21 |
+
<div class="flex w-full flex-col gap-5">
|
22 |
+
<div class="flex items-start justify-between text-xl font-semibold text-gray-800">
|
23 |
+
<h2>Application Settings</h2>
|
24 |
+
</div>
|
25 |
+
|
26 |
+
<div class="flex h-full flex-col gap-4 pt-4 max-sm:pt-0">
|
27 |
+
<!-- svelte-ignore a11y-label-has-associated-control -->
|
28 |
+
<label class="flex items-center">
|
29 |
+
<Switch
|
30 |
+
name="shareConversationsWithModelAuthors"
|
31 |
+
bind:checked={$settings.shareConversationsWithModelAuthors}
|
32 |
+
/>
|
33 |
+
<div class="inline cursor-pointer select-none items-center gap-2 pl-2">
|
34 |
+
Share conversations with model authors
|
35 |
+
</div>
|
36 |
+
</label>
|
37 |
+
|
38 |
+
<p class="text-sm text-gray-500">
|
39 |
+
Sharing your data will help improve the training data and make open models better over time.
|
40 |
+
</p>
|
41 |
+
|
42 |
+
<!-- svelte-ignore a11y-label-has-associated-control -->
|
43 |
+
<label class="mt-6 flex items-center">
|
44 |
+
<Switch name="hideEmojiOnSidebar" bind:checked={$settings.hideEmojiOnSidebar} />
|
45 |
+
<div class="inline cursor-pointer select-none items-center gap-2 pl-2">
|
46 |
+
Hide emoticons in conversation topics
|
47 |
+
</div>
|
48 |
+
</label>
|
49 |
+
|
50 |
+
<button
|
51 |
+
on:click|preventDefault={() => (isConfirmingDeletion = true)}
|
52 |
+
type="submit"
|
53 |
+
class="mt-6 flex items-center underline decoration-gray-300 underline-offset-2 hover:decoration-gray-700"
|
54 |
+
><CarbonTrashCan class="mr-2 inline text-sm text-red-500" />Delete all conversations</button
|
55 |
+
>
|
56 |
+
</div>
|
57 |
+
|
58 |
+
{#if isConfirmingDeletion}
|
59 |
+
<Modal on:close={() => (isConfirmingDeletion = false)}>
|
60 |
+
<form
|
61 |
+
use:enhance={() => {
|
62 |
+
dispatch("close");
|
63 |
+
}}
|
64 |
+
method="post"
|
65 |
+
action="{base}/conversations?/delete"
|
66 |
+
class="flex w-full flex-col gap-5 p-6"
|
67 |
+
>
|
68 |
+
<div class="flex items-start justify-between text-xl font-semibold text-gray-800">
|
69 |
+
<h2>Are you sure?</h2>
|
70 |
+
<button
|
71 |
+
type="button"
|
72 |
+
class="group"
|
73 |
+
on:click|stopPropagation={() => (isConfirmingDeletion = false)}
|
74 |
+
>
|
75 |
+
<CarbonClose class="text-gray-900 group-hover:text-gray-500" />
|
76 |
+
</button>
|
77 |
+
</div>
|
78 |
+
<p class="text-gray-800">
|
79 |
+
This action will delete all your conversations. This cannot be undone.
|
80 |
+
</p>
|
81 |
+
<button
|
82 |
+
type="submit"
|
83 |
+
class="mt-2 rounded-full bg-red-700 px-5 py-2 text-lg font-semibold text-gray-100 ring-gray-400 ring-offset-1 transition-all focus-visible:outline-none focus-visible:ring hover:ring"
|
84 |
+
>
|
85 |
+
Confirm deletion
|
86 |
+
</button>
|
87 |
+
</form>
|
88 |
+
</Modal>
|
89 |
+
{/if}
|
90 |
+
</div>
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
7 |
+
export async function POST({ request, locals }) {
|
8 |
+
const body = await request.json();
|
9 |
+
|
10 |
+
const { ethicsModalAccepted, ...settings } = z
|
11 |
+
.object({
|
12 |
+
shareConversationsWithModelAuthors: z
|
13 |
+
.boolean()
|
14 |
+
.default(DEFAULT_SETTINGS.shareConversationsWithModelAuthors),
|
15 |
+
hideEmojiOnSidebar: z.boolean().default(DEFAULT_SETTINGS.hideEmojiOnSidebar),
|
16 |
+
ethicsModalAccepted: z.boolean().optional(),
|
17 |
+
activeModel: validateModel(models).default(DEFAULT_SETTINGS.activeModel),
|
18 |
+
customPrompts: z.record(z.string()).default({}),
|
19 |
+
})
|
20 |
+
.parse(body);
|
21 |
+
|
22 |
+
await collections.settings.updateOne(
|
23 |
+
authCondition(locals),
|
24 |
+
{
|
25 |
+
$set: {
|
26 |
+
...settings,
|
27 |
+
...(ethicsModalAccepted && { ethicsModalAcceptedAt: new Date() }),
|
28 |
+
updatedAt: new Date(),
|
29 |
+
},
|
30 |
+
$setOnInsert: {
|
31 |
+
createdAt: new Date(),
|
32 |
+
},
|
33 |
+
},
|
34 |
+
{
|
35 |
+
upsert: true,
|
36 |
+
}
|
37 |
+
);
|
38 |
+
// return ok response
|
39 |
+
return new Response();
|
40 |
+
}
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { page } from "$app/stores";
|
3 |
+
import type { BackendModel } from "$lib/server/models";
|
4 |
+
import { useSettingsStore } from "$lib/stores/settings";
|
5 |
+
import CarbonArrowUpRight from "~icons/carbon/arrow-up-right";
|
6 |
+
|
7 |
+
const settings = useSettingsStore();
|
8 |
+
|
9 |
+
$: if ($settings.customPrompts[$page.params.model] === undefined) {
|
10 |
+
$settings.customPrompts = {
|
11 |
+
...$settings.customPrompts,
|
12 |
+
[$page.params.model]:
|
13 |
+
$page.data.models.find((el: BackendModel) => el.id === $page.params.model)?.preprompt || "",
|
14 |
+
};
|
15 |
+
}
|
16 |
+
|
17 |
+
$: hasCustomPreprompt =
|
18 |
+
$settings.customPrompts[$page.params.model] !==
|
19 |
+
$page.data.models.find((el: BackendModel) => el.id === $page.params.model)?.preprompt;
|
20 |
+
|
21 |
+
$: isActive = $settings.activeModel === $page.params.model;
|
22 |
+
|
23 |
+
$: model = $page.data.models.find((el: BackendModel) => el.id === $page.params.model);
|
24 |
+
</script>
|
25 |
+
|
26 |
+
<div class="flex flex-col items-start">
|
27 |
+
<h2 class="mb-2.5 text-xl font-semibold">
|
28 |
+
{$page.params.model}
|
29 |
+
</h2>
|
30 |
+
|
31 |
+
<div class="flex items-center gap-4">
|
32 |
+
<a
|
33 |
+
href={model.modelUrl || "https://huggingface.co/" + model.name}
|
34 |
+
target="_blank"
|
35 |
+
rel="noreferrer"
|
36 |
+
class="flex items-center truncate underline underline-offset-2"
|
37 |
+
>
|
38 |
+
<CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
|
39 |
+
Model page
|
40 |
+
</a>
|
41 |
+
|
42 |
+
{#if model.datasetName || model.datasetUrl}
|
43 |
+
<a
|
44 |
+
href={model.datasetUrl || "https://huggingface.co/datasets/" + model.datasetName}
|
45 |
+
target="_blank"
|
46 |
+
rel="noreferrer"
|
47 |
+
class="flex items-center truncate underline underline-offset-2"
|
48 |
+
>
|
49 |
+
<CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
|
50 |
+
Dataset page
|
51 |
+
</a>
|
52 |
+
{/if}
|
53 |
+
|
54 |
+
{#if model.websiteUrl}
|
55 |
+
<a
|
56 |
+
href={model.websiteUrl}
|
57 |
+
target="_blank"
|
58 |
+
class="flex items-center truncate underline underline-offset-2"
|
59 |
+
rel="noreferrer"
|
60 |
+
>
|
61 |
+
<CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
|
62 |
+
Model website
|
63 |
+
</a>
|
64 |
+
{/if}
|
65 |
+
</div>
|
66 |
+
|
67 |
+
<button
|
68 |
+
class="{isActive
|
69 |
+
? 'bg-gray-100'
|
70 |
+
: 'bg-black text-white'} my-8 flex items-center rounded-full px-3 py-1"
|
71 |
+
disabled={isActive}
|
72 |
+
name="Activate model"
|
73 |
+
on:click|stopPropagation={() => {
|
74 |
+
$settings.activeModel = $page.params.model;
|
75 |
+
}}
|
76 |
+
>
|
77 |
+
{isActive ? "Active model" : "Activate"}
|
78 |
+
</button>
|
79 |
+
|
80 |
+
<div class="flex w-full flex-col gap-2">
|
81 |
+
<div class="flex w-full flex-row content-between">
|
82 |
+
<h3 class="mb-1.5 text-lg font-semibold text-gray-800">System Prompt</h3>
|
83 |
+
{#if hasCustomPreprompt}
|
84 |
+
<button
|
85 |
+
class="ml-auto underline decoration-gray-300 hover:decoration-gray-700"
|
86 |
+
on:click|stopPropagation={() =>
|
87 |
+
($settings.customPrompts[$page.params.model] = model.preprompt)}
|
88 |
+
>
|
89 |
+
Reset
|
90 |
+
</button>
|
91 |
+
{/if}
|
92 |
+
</div>
|
93 |
+
<textarea
|
94 |
+
rows="10"
|
95 |
+
class="w-full resize-none rounded-md border-2 bg-gray-100 p-2"
|
96 |
+
bind:value={$settings.customPrompts[$page.params.model]}
|
97 |
+
/>
|
98 |
+
</div>
|
99 |
+
</div>
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
if (!data.models.map(({ id }) => id).includes(params.model)) {
|
8 |
+
throw redirect(302, `${base}/settings`);
|
9 |
+
}
|
10 |
+
|
11 |
+
return data;
|
12 |
+
}
|