Commit
•
82fcab7
1
Parent(s):
2804c18
Add settings (#134)
Browse filesCo-authored-by: Eliott C. <coyotte508@gmail.com>
Co-authored-by: coyotte508 <coyotte508@gmail.com>
Co-authored-by: Julien Chaumond <julien@huggingface.co>
- .env +0 -1
- PRIVACY.md +6 -4
- package.json +1 -1
- src/lib/components/EthicsModal.svelte +36 -44
- src/lib/components/NavMenu.svelte +9 -1
- src/lib/components/SettingsModal.svelte +59 -0
- src/lib/components/Switch.svelte +21 -0
- src/lib/server/database.ts +5 -1
- src/lib/types/AbortedGeneration.ts +2 -3
- src/lib/types/Conversation.ts +2 -4
- src/lib/types/Settings.ts +13 -0
- src/lib/types/SharedConversation.ts +2 -4
- src/lib/types/Timestamps.ts +4 -0
- src/lib/types/UrlDependency.ts +1 -0
- src/lib/updateSettings.ts +27 -0
- src/routes/+layout.server.ts +7 -0
- src/routes/+layout.svelte +10 -1
- src/routes/settings/+server.ts +34 -0
- svelte.config.js +3 -0
.env
CHANGED
@@ -15,7 +15,6 @@ PUBLIC_USER_MESSAGE_TOKEN=<|prompter|>
|
|
15 |
PUBLIC_ASSISTANT_MESSAGE_TOKEN=<|assistant|>
|
16 |
PUBLIC_SEP_TOKEN=</s>
|
17 |
PUBLIC_PREPROMPT="Below are a series of dialogues between various people and an AI assistant. The AI tries to be helpful, polite, honest, sophisticated, emotionally aware, and humble-but-knowledgeable. The assistant is happy to help with almost anything, and will do its best to understand exactly what is needed. It also tries to avoid giving false or misleading information, and it caveats when it isn't entirely sure about the right answer. That said, the assistant is practical and really does its best, and doesn't let caution get too much in the way of being useful."
|
18 |
-
PUBLIC_VERSION=0
|
19 |
PUBLIC_GOOGLE_ANALYTICS_ID=#G-XXXXXXXX / Leave empty to disable
|
20 |
|
21 |
# Copy this in .env.local with and replace "hf_<token>" your HF token from https://huggingface.co/settings/token
|
|
|
15 |
PUBLIC_ASSISTANT_MESSAGE_TOKEN=<|assistant|>
|
16 |
PUBLIC_SEP_TOKEN=</s>
|
17 |
PUBLIC_PREPROMPT="Below are a series of dialogues between various people and an AI assistant. The AI tries to be helpful, polite, honest, sophisticated, emotionally aware, and humble-but-knowledgeable. The assistant is happy to help with almost anything, and will do its best to understand exactly what is needed. It also tries to avoid giving false or misleading information, and it caveats when it isn't entirely sure about the right answer. That said, the assistant is practical and really does its best, and doesn't let caution get too much in the way of being useful."
|
|
|
18 |
PUBLIC_GOOGLE_ANALYTICS_ID=#G-XXXXXXXX / Leave empty to disable
|
19 |
|
20 |
# Copy this in .env.local with and replace "hf_<token>" your HF token from https://huggingface.co/settings/token
|
PRIVACY.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1 |
## Privacy
|
2 |
|
3 |
-
|
4 |
|
5 |
-
|
6 |
|
7 |
-
|
|
|
|
|
8 |
|
9 |
🗓 Please also consult huggingface.co's main privacy policy at https://huggingface.co/privacy. To exercise any of your legal privacy rights, please send an email to privacy@huggingface.co.
|
10 |
|
@@ -30,4 +32,4 @@ We welcome any feedback on this app: please participate to the public discussion
|
|
30 |
## Coming soon
|
31 |
|
32 |
- LLM watermarking
|
33 |
-
- User setting to share conversations with model authors
|
|
|
1 |
## Privacy
|
2 |
|
3 |
+
> Last updated: April 28, 2023
|
4 |
|
5 |
+
In this `v0.1` of HuggingChat, users are not authenticated in any way, i.e. this app doesn't have access to your HF user account even if you're logged in to huggingface.co. The app is only using an anonymous session cookie. ❗️ Warning ❗️ this means if you switch browsers or clear cookies, you will currently lose your conversations.
|
6 |
|
7 |
+
By default, your conversations are shared with the model's authors (for the `v0.1` model, to <a target="_blank" href="https://open-assistant.io/dashboard">Open Assistant</a>) to improve their training data and model over time. Model authors are the custodians of the data collected by their model, even if it's hosted on our platform.
|
8 |
+
|
9 |
+
If you disable data sharing in your settings, your conversations will not be used for any downstream usage (including for research or model training purposes), and they will only be stored to let you access past conversations. You can click on the Delete icon to delete any past conversation at any moment.
|
10 |
|
11 |
🗓 Please also consult huggingface.co's main privacy policy at https://huggingface.co/privacy. To exercise any of your legal privacy rights, please send an email to privacy@huggingface.co.
|
12 |
|
|
|
32 |
## Coming soon
|
33 |
|
34 |
- LLM watermarking
|
35 |
+
- User setting to share conversations with model authors (done ✅)
|
package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
{
|
2 |
"name": "chat-ui",
|
3 |
-
"version": "0.0
|
4 |
"private": true,
|
5 |
"scripts": {
|
6 |
"dev": "vite dev",
|
|
|
1 |
{
|
2 |
"name": "chat-ui",
|
3 |
+
"version": "0.1.0",
|
4 |
"private": true,
|
5 |
"scripts": {
|
6 |
"dev": "vite dev",
|
src/lib/components/EthicsModal.svelte
CHANGED
@@ -2,51 +2,43 @@
|
|
2 |
import { PUBLIC_VERSION } from "$env/static/public";
|
3 |
import Logo from "$lib/components/icons/Logo.svelte";
|
4 |
import Modal from "$lib/components/Modal.svelte";
|
5 |
-
import {
|
|
|
6 |
|
7 |
-
let
|
8 |
-
|
9 |
-
const LOCAL_STORAGE_KEY = "has-seen-ethics-modal";
|
10 |
-
|
11 |
-
onMount(() => {
|
12 |
-
ethicsModal = localStorage.getItem(LOCAL_STORAGE_KEY) === null;
|
13 |
-
});
|
14 |
-
|
15 |
-
const handleClick = () => {
|
16 |
-
ethicsModal = false;
|
17 |
-
localStorage.setItem(LOCAL_STORAGE_KEY, "true");
|
18 |
-
};
|
19 |
</script>
|
20 |
|
21 |
-
|
22 |
-
<
|
23 |
-
|
24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
>
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
class="ml-3 flex h-6 items-center rounded-lg border border-gray-100 bg-gray-50 px-2 text-base text-gray-400"
|
31 |
-
>
|
32 |
-
v{PUBLIC_VERSION}
|
33 |
-
</div>
|
34 |
-
{/if}
|
35 |
-
</h2>
|
36 |
-
<p class="px-4 text-lg font-semibold leading-snug text-gray-800 sm:px-12">
|
37 |
-
This application is for demonstration purposes only.
|
38 |
-
</p>
|
39 |
-
<p class="text-gray-800">
|
40 |
-
AI is an area of active research with known problems such as biased generation and
|
41 |
-
misinformation. Do not use this application for high-stakes decisions or advice.
|
42 |
-
</p>
|
43 |
-
<button
|
44 |
-
type="button"
|
45 |
-
on:click={handleClick}
|
46 |
-
class="mt-2 rounded-full bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-yellow-500"
|
47 |
-
>
|
48 |
-
Start chatting
|
49 |
-
</button>
|
50 |
-
</div>
|
51 |
-
</Modal>
|
52 |
-
{/if}
|
|
|
2 |
import { PUBLIC_VERSION } from "$env/static/public";
|
3 |
import Logo from "$lib/components/icons/Logo.svelte";
|
4 |
import Modal from "$lib/components/Modal.svelte";
|
5 |
+
import type { Settings } from "$lib/types/Settings";
|
6 |
+
import { updateSettings } from "$lib/updateSettings";
|
7 |
|
8 |
+
export let settings: Omit<Settings, "sessionId">;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
</script>
|
10 |
|
11 |
+
<Modal>
|
12 |
+
<div
|
13 |
+
class="flex w-full flex-col items-center gap-6 bg-gradient-to-t from-yellow-500/40 via-yellow-500/10 to-yellow-500/0 px-4 pb-10 pt-9 text-center"
|
14 |
+
>
|
15 |
+
<h2 class="flex items-center text-2xl font-semibold text-gray-800">
|
16 |
+
<Logo classNames="text-3xl mr-1.5" />HuggingChat
|
17 |
+
{#if typeof PUBLIC_VERSION !== "undefined"}
|
18 |
+
<div
|
19 |
+
class="ml-3 flex h-6 items-center rounded-lg border border-gray-100 bg-gray-50 px-2 text-base text-gray-400"
|
20 |
+
>
|
21 |
+
v{PUBLIC_VERSION}
|
22 |
+
</div>
|
23 |
+
{/if}
|
24 |
+
</h2>
|
25 |
+
<p class="px-4 text-lg font-semibold leading-snug text-gray-800 sm:px-12">
|
26 |
+
This application is for demonstration purposes only.
|
27 |
+
</p>
|
28 |
+
<p class="text-gray-800">
|
29 |
+
AI is an area of active research with known problems such as biased generation and
|
30 |
+
misinformation. Do not use this application for high-stakes decisions or advice.
|
31 |
+
</p>
|
32 |
+
<p class="px-2 text-sm text-gray-500">
|
33 |
+
Your conversations will be shared with model authors unless you disable it from your settings.
|
34 |
+
</p>
|
35 |
+
<!-- The updateSettings call will invalidate the settings, which will reload the page without the modal -->
|
36 |
+
<button
|
37 |
+
type="button"
|
38 |
+
on:click={() => updateSettings({ ...settings, ethicsModalAcceptedAt: new Date() })}
|
39 |
+
class="mt-2 rounded-full bg-black px-5 py-2 text-lg font-semibold text-gray-100 transition-colors hover:bg-yellow-500"
|
40 |
>
|
41 |
+
Start chatting
|
42 |
+
</button>
|
43 |
+
</div>
|
44 |
+
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/components/NavMenu.svelte
CHANGED
@@ -13,6 +13,7 @@
|
|
13 |
const dispatch = createEventDispatcher<{
|
14 |
shareConversation: { id: string; title: string };
|
15 |
deleteConversation: string;
|
|
|
16 |
editConversationTitle: { id: string; title: string };
|
17 |
}>();
|
18 |
|
@@ -75,7 +76,7 @@
|
|
75 |
{/each}
|
76 |
</div>
|
77 |
<div
|
78 |
-
class="mt-0.5 flex flex-col gap-
|
79 |
>
|
80 |
<button
|
81 |
on:click={switchTheme}
|
@@ -84,6 +85,13 @@
|
|
84 |
>
|
85 |
Theme
|
86 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
<a
|
88 |
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
|
89 |
target="_blank"
|
|
|
13 |
const dispatch = createEventDispatcher<{
|
14 |
shareConversation: { id: string; title: string };
|
15 |
deleteConversation: string;
|
16 |
+
clickSettings: void;
|
17 |
editConversationTitle: { id: string; title: string };
|
18 |
}>();
|
19 |
|
|
|
76 |
{/each}
|
77 |
</div>
|
78 |
<div
|
79 |
+
class="mt-0.5 flex flex-col gap-1 rounded-r-xl bg-gradient-to-l from-gray-50 p-3 text-sm dark:from-gray-800/30"
|
80 |
>
|
81 |
<button
|
82 |
on:click={switchTheme}
|
|
|
85 |
>
|
86 |
Theme
|
87 |
</button>
|
88 |
+
<button
|
89 |
+
on:click={() => dispatch("clickSettings")}
|
90 |
+
type="button"
|
91 |
+
class="group flex h-9 flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
92 |
+
>
|
93 |
+
Settings
|
94 |
+
</button>
|
95 |
<a
|
96 |
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
|
97 |
target="_blank"
|
src/lib/components/SettingsModal.svelte
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 type { Settings } from "$lib/types/Settings";
|
8 |
+
import { updateSettings } from "$lib/updateSettings";
|
9 |
+
|
10 |
+
export let settings: Pick<Settings, "shareConversationsWithModelAuthors">;
|
11 |
+
|
12 |
+
const dispatch = createEventDispatcher<{ close: void }>();
|
13 |
+
</script>
|
14 |
+
|
15 |
+
<Modal>
|
16 |
+
<div class="flex w-full flex-col gap-5 p-6">
|
17 |
+
<div class="flex items-start justify-between text-xl font-semibold text-gray-800">
|
18 |
+
<h2>Settings</h2>
|
19 |
+
<button class="group" on:click={() => dispatch("close")}>
|
20 |
+
<CarbonClose class="text-gray-900 group-hover:text-gray-500" />
|
21 |
+
</button>
|
22 |
+
</div>
|
23 |
+
|
24 |
+
<label class="flex cursor-pointer select-none items-center gap-2 text-gray-500" for="switch">
|
25 |
+
<Switch name="switch" bind:checked={settings.shareConversationsWithModelAuthors} />
|
26 |
+
Share conversations with model authors
|
27 |
+
</label>
|
28 |
+
|
29 |
+
<p class="text-gray-800">
|
30 |
+
Sharing your data will help improve the training data and make open models better over time.
|
31 |
+
</p>
|
32 |
+
<p class="text-gray-800">
|
33 |
+
Changing this setting will apply to all your conversations, past and future.
|
34 |
+
</p>
|
35 |
+
<p class="text-gray-800">
|
36 |
+
Read more about this model's authors,
|
37 |
+
<a
|
38 |
+
href="https://open-assistant.io/"
|
39 |
+
target="_blank"
|
40 |
+
rel="noreferrer"
|
41 |
+
class="underline decoration-gray-300 hover:decoration-gray-700">Open Assistant</a
|
42 |
+
>.
|
43 |
+
</p>
|
44 |
+
<button
|
45 |
+
type="button"
|
46 |
+
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-colors hover:ring"
|
47 |
+
on:click={() =>
|
48 |
+
updateSettings({
|
49 |
+
shareConversationsWithModelAuthors: settings.shareConversationsWithModelAuthors,
|
50 |
+
}).then((res) => {
|
51 |
+
if (res) {
|
52 |
+
dispatch("close");
|
53 |
+
}
|
54 |
+
})}
|
55 |
+
>
|
56 |
+
Apply
|
57 |
+
</button>
|
58 |
+
</div>
|
59 |
+
</Modal>
|
src/lib/components/Switch.svelte
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let checked: boolean;
|
3 |
+
export let name: string;
|
4 |
+
</script>
|
5 |
+
|
6 |
+
<div
|
7 |
+
class="relative inline-flex h-5 w-9 items-center rounded-full p-1 shadow-inner transition-all {checked
|
8 |
+
? 'bg-black'
|
9 |
+
: 'bg-gray-300 hover:bg-gray-400'}"
|
10 |
+
>
|
11 |
+
<input
|
12 |
+
bind:checked
|
13 |
+
type="checkbox"
|
14 |
+
{name}
|
15 |
+
id={name}
|
16 |
+
class="peer absolute inset-0 cursor-pointer opacity-0"
|
17 |
+
/>
|
18 |
+
<div
|
19 |
+
class="h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-all peer-checked:translate-x-3.5"
|
20 |
+
/>
|
21 |
+
</div>
|
src/lib/server/database.ts
CHANGED
@@ -3,6 +3,7 @@ import { MongoClient } from "mongodb";
|
|
3 |
import type { Conversation } from "$lib/types/Conversation";
|
4 |
import type { SharedConversation } from "$lib/types/SharedConversation";
|
5 |
import type { AbortedGeneration } from "$lib/types/AbortedGeneration";
|
|
|
6 |
|
7 |
const client = new MongoClient(MONGODB_URL, {
|
8 |
// directConnection: true
|
@@ -15,13 +16,16 @@ const db = client.db(MONGODB_DB_NAME);
|
|
15 |
const conversations = db.collection<Conversation>("conversations");
|
16 |
const sharedConversations = db.collection<SharedConversation>("sharedConversations");
|
17 |
const abortedGenerations = db.collection<AbortedGeneration>("abortedGenerations");
|
|
|
18 |
|
19 |
export { client, db };
|
20 |
-
export const collections = { conversations, sharedConversations, abortedGenerations };
|
21 |
|
22 |
client.on("open", () => {
|
23 |
conversations.createIndex({ sessionId: 1, updatedAt: -1 });
|
24 |
abortedGenerations.createIndex({ updatedAt: 1 }, { expireAfterSeconds: 30 });
|
25 |
abortedGenerations.createIndex({ conversationId: 1 }, { unique: true });
|
26 |
sharedConversations.createIndex({ hash: 1 }, { unique: true });
|
|
|
|
|
27 |
});
|
|
|
3 |
import type { Conversation } from "$lib/types/Conversation";
|
4 |
import type { SharedConversation } from "$lib/types/SharedConversation";
|
5 |
import type { AbortedGeneration } from "$lib/types/AbortedGeneration";
|
6 |
+
import type { Settings } from "$lib/types/Settings";
|
7 |
|
8 |
const client = new MongoClient(MONGODB_URL, {
|
9 |
// directConnection: true
|
|
|
16 |
const conversations = db.collection<Conversation>("conversations");
|
17 |
const sharedConversations = db.collection<SharedConversation>("sharedConversations");
|
18 |
const abortedGenerations = db.collection<AbortedGeneration>("abortedGenerations");
|
19 |
+
const settings = db.collection<Settings>("settings");
|
20 |
|
21 |
export { client, db };
|
22 |
+
export const collections = { conversations, sharedConversations, abortedGenerations, settings };
|
23 |
|
24 |
client.on("open", () => {
|
25 |
conversations.createIndex({ sessionId: 1, updatedAt: -1 });
|
26 |
abortedGenerations.createIndex({ updatedAt: 1 }, { expireAfterSeconds: 30 });
|
27 |
abortedGenerations.createIndex({ conversationId: 1 }, { unique: true });
|
28 |
sharedConversations.createIndex({ hash: 1 }, { unique: true });
|
29 |
+
// Sparse so that we can have settings on userId later
|
30 |
+
settings.createIndex({ sessionId: 1 }, { unique: true, sparse: true });
|
31 |
});
|
src/lib/types/AbortedGeneration.ts
CHANGED
@@ -1,9 +1,8 @@
|
|
1 |
// Ideally shouldn't be needed, see https://github.com/huggingface/chat-ui/pull/88#issuecomment-1523173850
|
2 |
|
3 |
import type { Conversation } from "./Conversation";
|
|
|
4 |
|
5 |
-
export interface AbortedGeneration {
|
6 |
-
createdAt: Date;
|
7 |
-
updatedAt: Date;
|
8 |
conversationId: Conversation["_id"];
|
9 |
}
|
|
|
1 |
// Ideally shouldn't be needed, see https://github.com/huggingface/chat-ui/pull/88#issuecomment-1523173850
|
2 |
|
3 |
import type { Conversation } from "./Conversation";
|
4 |
+
import type { Timestamps } from "./Timestamps";
|
5 |
|
6 |
+
export interface AbortedGeneration extends Timestamps {
|
|
|
|
|
7 |
conversationId: Conversation["_id"];
|
8 |
}
|
src/lib/types/Conversation.ts
CHANGED
@@ -1,7 +1,8 @@
|
|
1 |
import type { ObjectId } from "mongodb";
|
2 |
import type { Message } from "./Message";
|
|
|
3 |
|
4 |
-
export interface Conversation {
|
5 |
_id: ObjectId;
|
6 |
|
7 |
// Can be undefined for shared convo then deleted
|
@@ -10,9 +11,6 @@ export interface Conversation {
|
|
10 |
title: string;
|
11 |
messages: Message[];
|
12 |
|
13 |
-
createdAt: Date;
|
14 |
-
updatedAt: Date;
|
15 |
-
|
16 |
meta?: {
|
17 |
fromShareId?: string;
|
18 |
};
|
|
|
1 |
import type { ObjectId } from "mongodb";
|
2 |
import type { Message } from "./Message";
|
3 |
+
import type { Timestamps } from "./Timestamps";
|
4 |
|
5 |
+
export interface Conversation extends Timestamps {
|
6 |
_id: ObjectId;
|
7 |
|
8 |
// Can be undefined for shared convo then deleted
|
|
|
11 |
title: string;
|
12 |
messages: Message[];
|
13 |
|
|
|
|
|
|
|
14 |
meta?: {
|
15 |
fromShareId?: string;
|
16 |
};
|
src/lib/types/Settings.ts
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { Timestamps } from "./Timestamps";
|
2 |
+
|
3 |
+
export interface Settings extends Timestamps {
|
4 |
+
sessionId: string;
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Note: Only conversations with this settings explictly set to true should be shared.
|
8 |
+
*
|
9 |
+
* This setting is explicitly set to true when users accept the ethics modal.
|
10 |
+
* */
|
11 |
+
shareConversationsWithModelAuthors: boolean;
|
12 |
+
ethicsModalAcceptedAt: Date | null;
|
13 |
+
}
|
src/lib/types/SharedConversation.ts
CHANGED
@@ -1,13 +1,11 @@
|
|
1 |
import type { Message } from "./Message";
|
|
|
2 |
|
3 |
-
export interface SharedConversation {
|
4 |
_id: string;
|
5 |
|
6 |
hash: string;
|
7 |
|
8 |
title: string;
|
9 |
messages: Message[];
|
10 |
-
|
11 |
-
createdAt: Date;
|
12 |
-
updatedAt: Date;
|
13 |
}
|
|
|
1 |
import type { Message } from "./Message";
|
2 |
+
import type { Timestamps } from "./Timestamps";
|
3 |
|
4 |
+
export interface SharedConversation extends Timestamps {
|
5 |
_id: string;
|
6 |
|
7 |
hash: string;
|
8 |
|
9 |
title: string;
|
10 |
messages: Message[];
|
|
|
|
|
|
|
11 |
}
|
src/lib/types/Timestamps.ts
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface Timestamps {
|
2 |
+
createdAt: Date;
|
3 |
+
updatedAt: Date;
|
4 |
+
}
|
src/lib/types/UrlDependency.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
/* eslint-disable no-shadow */
|
2 |
export enum UrlDependency {
|
3 |
ConversationList = "conversation:list",
|
|
|
4 |
}
|
|
|
1 |
/* eslint-disable no-shadow */
|
2 |
export enum UrlDependency {
|
3 |
ConversationList = "conversation:list",
|
4 |
+
Settings = "settings:list",
|
5 |
}
|
src/lib/updateSettings.ts
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { invalidate } from "$app/navigation";
|
2 |
+
import { base } from "$app/paths";
|
3 |
+
import { error } from "$lib/stores/errors";
|
4 |
+
import type { Settings } from "./types/Settings";
|
5 |
+
import { UrlDependency } from "./types/UrlDependency";
|
6 |
+
|
7 |
+
export async function updateSettings(
|
8 |
+
settings: Partial<Omit<Settings, "sessionId">>
|
9 |
+
): Promise<boolean> {
|
10 |
+
try {
|
11 |
+
const res = await fetch(`${base}/settings`, {
|
12 |
+
method: "PATCH",
|
13 |
+
headers: { "Content-Type": "application/json" },
|
14 |
+
body: JSON.stringify(settings),
|
15 |
+
});
|
16 |
+
if (!res.ok) {
|
17 |
+
error.set("Error while updating settings, try again.");
|
18 |
+
return false;
|
19 |
+
}
|
20 |
+
await invalidate(UrlDependency.Settings);
|
21 |
+
return true;
|
22 |
+
} catch (err) {
|
23 |
+
console.error(err);
|
24 |
+
error.set(String(err));
|
25 |
+
return false;
|
26 |
+
}
|
27 |
+
}
|
src/routes/+layout.server.ts
CHANGED
@@ -7,6 +7,9 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
7 |
const { conversations } = collections;
|
8 |
|
9 |
depends(UrlDependency.ConversationList);
|
|
|
|
|
|
|
10 |
|
11 |
return {
|
12 |
conversations: await conversations
|
@@ -22,5 +25,9 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
22 |
})
|
23 |
.map((conv) => ({ id: conv._id.toString(), title: conv.title }))
|
24 |
.toArray(),
|
|
|
|
|
|
|
|
|
25 |
};
|
26 |
};
|
|
|
7 |
const { conversations } = collections;
|
8 |
|
9 |
depends(UrlDependency.ConversationList);
|
10 |
+
depends(UrlDependency.Settings);
|
11 |
+
|
12 |
+
const settings = await collections.settings.findOne({ sessionId: locals.sessionId });
|
13 |
|
14 |
return {
|
15 |
conversations: await conversations
|
|
|
25 |
})
|
26 |
.map((conv) => ({ id: conv._id.toString(), title: conv.title }))
|
27 |
.toArray(),
|
28 |
+
settings: {
|
29 |
+
shareConversationsWithModelAuthors: settings?.shareConversationsWithModelAuthors ?? true,
|
30 |
+
ethicsModalAcceptedAt: settings?.ethicsModalAcceptedAt ?? null,
|
31 |
+
},
|
32 |
};
|
33 |
};
|
src/routes/+layout.svelte
CHANGED
@@ -14,10 +14,12 @@
|
|
14 |
import NavMenu from "$lib/components/NavMenu.svelte";
|
15 |
import Toast from "$lib/components/Toast.svelte";
|
16 |
import EthicsModal from "$lib/components/EthicsModal.svelte";
|
|
|
17 |
|
18 |
export let data;
|
19 |
|
20 |
let isNavOpen = false;
|
|
|
21 |
let errorToastTimeout: NodeJS.Timeout;
|
22 |
let currentError: string | null;
|
23 |
|
@@ -113,6 +115,7 @@
|
|
113 |
conversations={data.conversations}
|
114 |
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
115 |
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
|
|
116 |
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
|
117 |
/>
|
118 |
</MobileNav>
|
@@ -121,12 +124,18 @@
|
|
121 |
conversations={data.conversations}
|
122 |
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
123 |
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
|
|
124 |
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
|
125 |
/>
|
126 |
</nav>
|
127 |
{#if currentError}
|
128 |
<Toast message={currentError} />
|
129 |
{/if}
|
130 |
-
|
|
|
|
|
|
|
|
|
|
|
131 |
<slot />
|
132 |
</div>
|
|
|
14 |
import NavMenu from "$lib/components/NavMenu.svelte";
|
15 |
import Toast from "$lib/components/Toast.svelte";
|
16 |
import EthicsModal from "$lib/components/EthicsModal.svelte";
|
17 |
+
import SettingsModal from "$lib/components/SettingsModal.svelte";
|
18 |
|
19 |
export let data;
|
20 |
|
21 |
let isNavOpen = false;
|
22 |
+
let isSettingsOpen = false;
|
23 |
let errorToastTimeout: NodeJS.Timeout;
|
24 |
let currentError: string | null;
|
25 |
|
|
|
115 |
conversations={data.conversations}
|
116 |
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
117 |
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
118 |
+
on:clickSettings={() => (isSettingsOpen = true)}
|
119 |
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
|
120 |
/>
|
121 |
</MobileNav>
|
|
|
124 |
conversations={data.conversations}
|
125 |
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
126 |
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
127 |
+
on:clickSettings={() => (isSettingsOpen = true)}
|
128 |
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
|
129 |
/>
|
130 |
</nav>
|
131 |
{#if currentError}
|
132 |
<Toast message={currentError} />
|
133 |
{/if}
|
134 |
+
{#if isSettingsOpen}
|
135 |
+
<SettingsModal on:close={() => (isSettingsOpen = false)} settings={data.settings} />
|
136 |
+
{/if}
|
137 |
+
{#if !data.settings.ethicsModalAcceptedAt}
|
138 |
+
<EthicsModal settings={data.settings} />
|
139 |
+
{/if}
|
140 |
<slot />
|
141 |
</div>
|
src/routes/settings/+server.ts
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { collections } from "$lib/server/database.js";
|
2 |
+
import { subMinutes } from "date-fns";
|
3 |
+
import { z } from "zod";
|
4 |
+
|
5 |
+
export async function PATCH({ locals, request }) {
|
6 |
+
const json = await request.json();
|
7 |
+
|
8 |
+
const settings = z
|
9 |
+
.object({
|
10 |
+
shareConversationsWithModelAuthors: z.boolean().default(true),
|
11 |
+
ethicsModalAcceptedAt: z.optional(z.date({ coerce: true }).min(subMinutes(new Date(), 5))),
|
12 |
+
})
|
13 |
+
.parse(json);
|
14 |
+
|
15 |
+
await collections.settings.updateOne(
|
16 |
+
{
|
17 |
+
sessionId: locals.sessionId,
|
18 |
+
},
|
19 |
+
{
|
20 |
+
$set: {
|
21 |
+
...settings,
|
22 |
+
updatedAt: new Date(),
|
23 |
+
},
|
24 |
+
$setOnInsert: {
|
25 |
+
createdAt: new Date(),
|
26 |
+
},
|
27 |
+
},
|
28 |
+
{
|
29 |
+
upsert: true,
|
30 |
+
}
|
31 |
+
);
|
32 |
+
|
33 |
+
return new Response();
|
34 |
+
}
|
svelte.config.js
CHANGED
@@ -1,10 +1,13 @@
|
|
1 |
import adapter from "@sveltejs/adapter-node";
|
2 |
import { vitePreprocess } from "@sveltejs/kit/vite";
|
3 |
import dotenv from "dotenv";
|
|
|
4 |
|
5 |
dotenv.config({ path: "./.env.local" });
|
6 |
dotenv.config({ path: "./.env" });
|
7 |
|
|
|
|
|
8 |
/** @type {import('@sveltejs/kit').Config} */
|
9 |
const config = {
|
10 |
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
|
|
1 |
import adapter from "@sveltejs/adapter-node";
|
2 |
import { vitePreprocess } from "@sveltejs/kit/vite";
|
3 |
import dotenv from "dotenv";
|
4 |
+
import pkg from "./package.json" assert { type: "json" };
|
5 |
|
6 |
dotenv.config({ path: "./.env.local" });
|
7 |
dotenv.config({ path: "./.env" });
|
8 |
|
9 |
+
process.env.PUBLIC_VERSION = pkg.version.replace(/\.0\b/g, "");
|
10 |
+
|
11 |
/** @type {import('@sveltejs/kit').Config} */
|
12 |
const config = {
|
13 |
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|