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; multimodalOverrides: Record; recentlySaved: boolean; disableStream: boolean; directPaste: boolean; hidePromptExamples: Record; }; type SettingsStoreWritable = Writable & { instantSet: (settings: Partial) => Promise; initValue: ( key: K, nestedKey: string, value: string | boolean ) => Promise; }; export function useSettingsStore() { return getContext("settings"); } export function createSettingsStore(initialValue: Omit) { const baseStore = writable({ ...initialValue, recentlySaved: false }); let timeoutId: NodeJS.Timeout; let showSavedOnNextSync = false; async function setSettings(settings: Partial) { 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( key: K, nestedKey: string, value: string | boolean ) { const currentStore = get(baseStore); const currentNestedObject = currentStore[key] as Record; // 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) { 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; }