chat-ui / src /lib /stores /settings.ts
coyotte508
A new start
fc69895
import { browser } from "$app/environment";
import { invalidate } from "$app/navigation";
import { base } from "$app/paths";
import { UrlDependency } from "$lib/types/UrlDependency";
import { getContext, setContext } from "svelte";
import { type Writable, writable, get } from "svelte/store";
type SettingsStore = {
shareConversationsWithModelAuthors: boolean;
welcomeModalSeen: boolean;
welcomeModalSeenAt: Date | null;
activeModel: string;
customPrompts: Record<string, string>;
multimodalOverrides: Record<string, boolean>;
recentlySaved: boolean;
disableStream: boolean;
directPaste: boolean;
hidePromptExamples: Record<string, boolean>;
};
type SettingsStoreWritable = Writable<SettingsStore> & {
instantSet: (settings: Partial<SettingsStore>) => Promise<void>;
initValue: <K extends keyof SettingsStore>(
key: K,
nestedKey: string,
value: string | boolean
) => Promise<void>;
};
export function useSettingsStore() {
return getContext<SettingsStoreWritable>("settings");
}
export function createSettingsStore(initialValue: Omit<SettingsStore, "recentlySaved">) {
const baseStore = writable({ ...initialValue, recentlySaved: false });
let timeoutId: NodeJS.Timeout;
let showSavedOnNextSync = false;
async function setSettings(settings: Partial<SettingsStore>) {
baseStore.update((s) => ({
...s,
...settings,
}));
if (browser) {
showSavedOnNextSync = true; // User edit, should show "Saved"
clearTimeout(timeoutId);
timeoutId = setTimeout(async () => {
await fetch(`${base}/settings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(get(baseStore)),
});
invalidate(UrlDependency.ConversationList);
if (showSavedOnNextSync) {
// set savedRecently to true for 3s
baseStore.update((s) => ({
...s,
recentlySaved: true,
}));
setTimeout(() => {
baseStore.update((s) => ({
...s,
recentlySaved: false,
}));
}, 3000);
}
showSavedOnNextSync = false;
}, 300);
// debounce server calls by 300ms
}
}
async function initValue<K extends keyof SettingsStore>(
key: K,
nestedKey: string,
value: string | boolean
) {
const currentStore = get(baseStore);
const currentNestedObject = currentStore[key] as Record<string, string | boolean>;
// Only initialize if undefined
if (currentNestedObject?.[nestedKey] !== undefined) {
return;
}
// Update the store
const newNestedObject = {
...(currentNestedObject || {}),
[nestedKey]: value,
};
baseStore.update((s) => ({
...s,
[key]: newNestedObject,
}));
// Save to server (debounced) - note: we don't set showSavedOnNextSync
if (browser) {
clearTimeout(timeoutId);
timeoutId = setTimeout(async () => {
await fetch(`${base}/settings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(get(baseStore)),
});
invalidate(UrlDependency.ConversationList);
if (showSavedOnNextSync) {
baseStore.update((s) => ({
...s,
recentlySaved: true,
}));
setTimeout(() => {
baseStore.update((s) => ({
...s,
recentlySaved: false,
}));
}, 3000);
}
showSavedOnNextSync = false;
}, 300);
}
}
async function instantSet(settings: Partial<SettingsStore>) {
baseStore.update((s) => ({
...s,
...settings,
}));
if (browser) {
await fetch(`${base}/settings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...get(baseStore),
...settings,
}),
});
invalidate(UrlDependency.ConversationList);
}
}
const newStore = {
subscribe: baseStore.subscribe,
set: setSettings,
instantSet,
initValue,
update: (fn: (s: SettingsStore) => SettingsStore) => {
setSettings(fn(get(baseStore)));
},
} satisfies SettingsStoreWritable;
setContext("settings", newStore);
return newStore;
}