Add initValue to settings store and refactor model settings init
Browse filesIntroduced an initValue method to the settings store for initializing nested settings keys only if undefined. Refactored the settings page to use this method for initializing customPrompts, multimodalOverrides, and hidePromptExamples, improving consistency and preventing unnecessary overwrites.
src/lib/stores/settings.ts
CHANGED
|
@@ -20,6 +20,11 @@ type SettingsStore = {
|
|
| 20 |
|
| 21 |
type SettingsStoreWritable = Writable<SettingsStore> & {
|
| 22 |
instantSet: (settings: Partial<SettingsStore>) => Promise<void>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
};
|
| 24 |
|
| 25 |
export function useSettingsStore() {
|
|
@@ -30,6 +35,7 @@ export function createSettingsStore(initialValue: Omit<SettingsStore, "recentlyS
|
|
| 30 |
const baseStore = writable({ ...initialValue, recentlySaved: false });
|
| 31 |
|
| 32 |
let timeoutId: NodeJS.Timeout;
|
|
|
|
| 33 |
|
| 34 |
async function setSettings(settings: Partial<SettingsStore>) {
|
| 35 |
baseStore.update((s) => ({
|
|
@@ -38,6 +44,7 @@ export function createSettingsStore(initialValue: Omit<SettingsStore, "recentlyS
|
|
| 38 |
}));
|
| 39 |
|
| 40 |
if (browser) {
|
|
|
|
| 41 |
clearTimeout(timeoutId);
|
| 42 |
timeoutId = setTimeout(async () => {
|
| 43 |
await fetch(`${base}/settings`, {
|
|
@@ -45,28 +52,86 @@ export function createSettingsStore(initialValue: Omit<SettingsStore, "recentlyS
|
|
| 45 |
headers: {
|
| 46 |
"Content-Type": "application/json",
|
| 47 |
},
|
| 48 |
-
body: JSON.stringify(
|
| 49 |
-
...get(baseStore),
|
| 50 |
-
...settings,
|
| 51 |
-
}),
|
| 52 |
});
|
| 53 |
|
| 54 |
invalidate(UrlDependency.ConversationList);
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
recentlySaved: true,
|
| 59 |
-
}));
|
| 60 |
-
setTimeout(() => {
|
| 61 |
baseStore.update((s) => ({
|
| 62 |
...s,
|
| 63 |
-
recentlySaved:
|
| 64 |
}));
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}, 300);
|
| 67 |
// debounce server calls by 300ms
|
| 68 |
}
|
| 69 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
async function instantSet(settings: Partial<SettingsStore>) {
|
| 71 |
baseStore.update((s) => ({
|
| 72 |
...s,
|
|
@@ -92,6 +157,7 @@ export function createSettingsStore(initialValue: Omit<SettingsStore, "recentlyS
|
|
| 92 |
subscribe: baseStore.subscribe,
|
| 93 |
set: setSettings,
|
| 94 |
instantSet,
|
|
|
|
| 95 |
update: (fn: (s: SettingsStore) => SettingsStore) => {
|
| 96 |
setSettings(fn(get(baseStore)));
|
| 97 |
},
|
|
|
|
| 20 |
|
| 21 |
type SettingsStoreWritable = Writable<SettingsStore> & {
|
| 22 |
instantSet: (settings: Partial<SettingsStore>) => Promise<void>;
|
| 23 |
+
initValue: <K extends keyof SettingsStore>(
|
| 24 |
+
key: K,
|
| 25 |
+
nestedKey: string,
|
| 26 |
+
value: any
|
| 27 |
+
) => Promise<void>;
|
| 28 |
};
|
| 29 |
|
| 30 |
export function useSettingsStore() {
|
|
|
|
| 35 |
const baseStore = writable({ ...initialValue, recentlySaved: false });
|
| 36 |
|
| 37 |
let timeoutId: NodeJS.Timeout;
|
| 38 |
+
let showSavedOnNextSync = false;
|
| 39 |
|
| 40 |
async function setSettings(settings: Partial<SettingsStore>) {
|
| 41 |
baseStore.update((s) => ({
|
|
|
|
| 44 |
}));
|
| 45 |
|
| 46 |
if (browser) {
|
| 47 |
+
showSavedOnNextSync = true; // User edit, should show "Saved"
|
| 48 |
clearTimeout(timeoutId);
|
| 49 |
timeoutId = setTimeout(async () => {
|
| 50 |
await fetch(`${base}/settings`, {
|
|
|
|
| 52 |
headers: {
|
| 53 |
"Content-Type": "application/json",
|
| 54 |
},
|
| 55 |
+
body: JSON.stringify(get(baseStore)),
|
|
|
|
|
|
|
|
|
|
| 56 |
});
|
| 57 |
|
| 58 |
invalidate(UrlDependency.ConversationList);
|
| 59 |
+
|
| 60 |
+
if (showSavedOnNextSync) {
|
| 61 |
+
// set savedRecently to true for 3s
|
|
|
|
|
|
|
|
|
|
| 62 |
baseStore.update((s) => ({
|
| 63 |
...s,
|
| 64 |
+
recentlySaved: true,
|
| 65 |
}));
|
| 66 |
+
setTimeout(() => {
|
| 67 |
+
baseStore.update((s) => ({
|
| 68 |
+
...s,
|
| 69 |
+
recentlySaved: false,
|
| 70 |
+
}));
|
| 71 |
+
}, 3000);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
showSavedOnNextSync = false;
|
| 75 |
}, 300);
|
| 76 |
// debounce server calls by 300ms
|
| 77 |
}
|
| 78 |
}
|
| 79 |
+
|
| 80 |
+
async function initValue<K extends keyof SettingsStore>(
|
| 81 |
+
key: K,
|
| 82 |
+
nestedKey: string,
|
| 83 |
+
value: any
|
| 84 |
+
) {
|
| 85 |
+
const currentStore = get(baseStore);
|
| 86 |
+
const currentNestedObject = currentStore[key] as Record<string, any>;
|
| 87 |
+
|
| 88 |
+
// Only initialize if undefined
|
| 89 |
+
if (currentNestedObject?.[nestedKey] !== undefined) {
|
| 90 |
+
return;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
// Update the store
|
| 94 |
+
const newNestedObject = {
|
| 95 |
+
...(currentNestedObject || {}),
|
| 96 |
+
[nestedKey]: value,
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
baseStore.update((s) => ({
|
| 100 |
+
...s,
|
| 101 |
+
[key]: newNestedObject,
|
| 102 |
+
}));
|
| 103 |
+
|
| 104 |
+
// Save to server (debounced) - note: we don't set showSavedOnNextSync
|
| 105 |
+
if (browser) {
|
| 106 |
+
clearTimeout(timeoutId);
|
| 107 |
+
timeoutId = setTimeout(async () => {
|
| 108 |
+
await fetch(`${base}/settings`, {
|
| 109 |
+
method: "POST",
|
| 110 |
+
headers: {
|
| 111 |
+
"Content-Type": "application/json",
|
| 112 |
+
},
|
| 113 |
+
body: JSON.stringify(get(baseStore)),
|
| 114 |
+
});
|
| 115 |
+
|
| 116 |
+
invalidate(UrlDependency.ConversationList);
|
| 117 |
+
|
| 118 |
+
if (showSavedOnNextSync) {
|
| 119 |
+
baseStore.update((s) => ({
|
| 120 |
+
...s,
|
| 121 |
+
recentlySaved: true,
|
| 122 |
+
}));
|
| 123 |
+
setTimeout(() => {
|
| 124 |
+
baseStore.update((s) => ({
|
| 125 |
+
...s,
|
| 126 |
+
recentlySaved: false,
|
| 127 |
+
}));
|
| 128 |
+
}, 3000);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
showSavedOnNextSync = false;
|
| 132 |
+
}, 300);
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
async function instantSet(settings: Partial<SettingsStore>) {
|
| 136 |
baseStore.update((s) => ({
|
| 137 |
...s,
|
|
|
|
| 157 |
subscribe: baseStore.subscribe,
|
| 158 |
set: setSettings,
|
| 159 |
instantSet,
|
| 160 |
+
initValue,
|
| 161 |
update: (fn: (s: SettingsStore) => SettingsStore) => {
|
| 162 |
setSettings(fn(get(baseStore)));
|
| 163 |
},
|
src/routes/settings/(nav)/[...model]/+page.svelte
CHANGED
|
@@ -21,13 +21,9 @@
|
|
| 21 |
type RouterProvider = { provider: string } & Record<string, unknown>;
|
| 22 |
|
| 23 |
$effect(() => {
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
[page.params.model]:
|
| 28 |
-
page.data.models.find((el: BackendModel) => el.id === page.params.model)?.preprompt || "",
|
| 29 |
-
};
|
| 30 |
-
}
|
| 31 |
});
|
| 32 |
|
| 33 |
let hasCustomPreprompt = $derived(
|
|
@@ -40,31 +36,15 @@
|
|
| 40 |
|
| 41 |
// Initialize multimodal override for this model if not set yet
|
| 42 |
$effect(() => {
|
| 43 |
-
if (
|
| 44 |
-
$settings.multimodalOverrides = {};
|
| 45 |
-
}
|
| 46 |
-
const modelId = page.params.model;
|
| 47 |
-
if ($settings.multimodalOverrides[modelId] === undefined && model) {
|
| 48 |
// Default to the model's advertised capability
|
| 49 |
-
|
| 50 |
-
...$settings.multimodalOverrides,
|
| 51 |
-
[modelId]: !!model.multimodal,
|
| 52 |
-
};
|
| 53 |
}
|
| 54 |
});
|
| 55 |
|
| 56 |
// Ensure hidePromptExamples has an entry for this model so the switch can bind safely
|
| 57 |
$effect(() => {
|
| 58 |
-
|
| 59 |
-
$settings.hidePromptExamples = {};
|
| 60 |
-
}
|
| 61 |
-
const modelId = page.params.model;
|
| 62 |
-
if ($settings.hidePromptExamples[modelId] === undefined) {
|
| 63 |
-
$settings.hidePromptExamples = {
|
| 64 |
-
...$settings.hidePromptExamples,
|
| 65 |
-
[modelId]: false,
|
| 66 |
-
};
|
| 67 |
-
}
|
| 68 |
});
|
| 69 |
</script>
|
| 70 |
|
|
|
|
| 21 |
type RouterProvider = { provider: string } & Record<string, unknown>;
|
| 22 |
|
| 23 |
$effect(() => {
|
| 24 |
+
const defaultPreprompt =
|
| 25 |
+
page.data.models.find((el: BackendModel) => el.id === page.params.model)?.preprompt || "";
|
| 26 |
+
settings.initValue("customPrompts", page.params.model, defaultPreprompt);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
});
|
| 28 |
|
| 29 |
let hasCustomPreprompt = $derived(
|
|
|
|
| 36 |
|
| 37 |
// Initialize multimodal override for this model if not set yet
|
| 38 |
$effect(() => {
|
| 39 |
+
if (model) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
// Default to the model's advertised capability
|
| 41 |
+
settings.initValue("multimodalOverrides", page.params.model, !!model.multimodal);
|
|
|
|
|
|
|
|
|
|
| 42 |
}
|
| 43 |
});
|
| 44 |
|
| 45 |
// Ensure hidePromptExamples has an entry for this model so the switch can bind safely
|
| 46 |
$effect(() => {
|
| 47 |
+
settings.initValue("hidePromptExamples", page.params.model, false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
});
|
| 49 |
</script>
|
| 50 |
|