mirror of
https://github.com/LostRuins/koboldcpp.git
synced 2026-08-29 02:04:12 +00:00
ui: read persisted settings before the API key probe (#27365)
The route loads run ahead of the root layout script, so validateApiKey read the settings store while it still held factory defaults and probed /props without the stored key. initStores() now hands the same startup promise to every caller and the chat loads await it before probing. The one-time admin baseline no longer overwrites a key the user has already set: on a first visit the config carries factory values only, so a diverging key comes from the user and wins.
This commit is contained in:
parent
95c409c136
commit
77acca437f
5 changed files with 52 additions and 18 deletions
|
|
@ -1,10 +1,15 @@
|
|||
/**
|
||||
* Explicit store initialization, called once from the root layout.
|
||||
* Explicit store initialization, run once and shared by every caller.
|
||||
*
|
||||
* Order matters: migrations run first because they rename and rewrite
|
||||
* localStorage keys, so every store that reads localStorage initializes
|
||||
* only after they complete. Constructors and module-level side effects
|
||||
* stay empty so import order can no longer change startup behavior.
|
||||
*
|
||||
* The returned promise resolves once the persisted state is in memory, which
|
||||
* route loads await before reading settings: they run ahead of the root layout
|
||||
* script. The conversation list loads in the background, awaited by the chat
|
||||
* page that renders it.
|
||||
*/
|
||||
|
||||
// direct imports, not via the barrel, to avoid circular deps
|
||||
|
|
@ -16,19 +21,20 @@ import { versionStore } from './version.svelte';
|
|||
import { browser } from '$app/environment';
|
||||
import { MigrationService } from '$lib/services/migration.service';
|
||||
|
||||
let started = false;
|
||||
let startup: Promise<void> | null = null;
|
||||
|
||||
export async function initStores(): Promise<void> {
|
||||
if (!browser || started) return;
|
||||
export function initStores(): Promise<void> {
|
||||
if (!browser) return Promise.resolve();
|
||||
|
||||
started = true;
|
||||
startup ??= (async () => {
|
||||
await MigrationService.runAllMigrations();
|
||||
|
||||
await MigrationService.runAllMigrations();
|
||||
settingsStore.initialize();
|
||||
permissionsStore.initialize();
|
||||
toolsStore.initialize();
|
||||
void versionStore.initialize();
|
||||
void conversationsStore.init();
|
||||
})();
|
||||
|
||||
settingsStore.initialize();
|
||||
permissionsStore.initialize();
|
||||
toolsStore.initialize();
|
||||
void versionStore.initialize();
|
||||
|
||||
await conversationsStore.init();
|
||||
return startup;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -358,17 +358,24 @@ class SettingsStore {
|
|||
// UI settings are the admin's defaults for new users: applied once on
|
||||
// the first visit, never on later loads, so the user's config can
|
||||
// diverge. "Reset to Default" is the explicit way back to the baseline.
|
||||
// A first visit config carries factory values only, so a key that
|
||||
// already diverges here was set by the user before the baseline could
|
||||
// be reached, through the API key splash, and stays theirs.
|
||||
if (uiSettings && this.isFirstVisit) {
|
||||
this.isFirstVisit = false;
|
||||
|
||||
for (const [key, value] of Object.entries(uiSettings)) {
|
||||
if (!this.userOverrides.has(key) && value !== undefined) {
|
||||
setConfigValue(this.config, key, value);
|
||||
if (value === undefined || this.userOverrides.has(key)) continue;
|
||||
|
||||
// theme lives in mode-watcher, not just in config -> propagate
|
||||
if (key === SETTINGS_KEYS.THEME) {
|
||||
setMode(value as ColorMode);
|
||||
}
|
||||
if (getConfigValue(this.config, key) !== getConfigValue(SETTING_CONFIG_DEFAULT, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setConfigValue(this.config, key, value);
|
||||
|
||||
// theme lives in mode-watcher, not just in config -> propagate
|
||||
if (key === SETTINGS_KEYS.THEME) {
|
||||
setMode(value as ColorMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import type { PageLoad } from './$types';
|
||||
import { initStores } from '$lib/stores/init';
|
||||
import { validateApiKey } from '$lib/utils';
|
||||
|
||||
export const load: PageLoad = async ({ fetch }) => {
|
||||
// loads run before the root layout script, so the stored API key reaches
|
||||
// the probe only once the settings store has read localStorage
|
||||
await initStores();
|
||||
await validateApiKey(fetch);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import type { PageLoad } from './$types';
|
||||
import { initStores } from '$lib/stores/init';
|
||||
import { validateApiKey } from '$lib/utils';
|
||||
|
||||
export const load: PageLoad = async ({ fetch }) => {
|
||||
// loads run before the root layout script, so the stored API key reaches
|
||||
// the probe only once the settings store has read localStorage
|
||||
await initStores();
|
||||
await validateApiKey(fetch);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,6 +47,19 @@ describe('server ui_settings application semantics', () => {
|
|||
expect(stored.apiKey).toBe('sk-user-key');
|
||||
});
|
||||
|
||||
it('keeps a value the user sets before the baseline is reachable', () => {
|
||||
settingsStore.initialize();
|
||||
// the splash is the only way in when the server runs with --api-key,
|
||||
// so the first user write lands before the first successful /props
|
||||
settingsStore.updateConfig('apiKey', 'sk-user-key');
|
||||
mockProps({ apiKey: 'admin-placeholder', theme: 'dark' });
|
||||
|
||||
settingsStore.syncWithServerDefaults();
|
||||
|
||||
expect(settingsStore.config.apiKey).toBe('sk-user-key');
|
||||
expect(settingsStore.config.theme).toBe('dark');
|
||||
});
|
||||
|
||||
it('Reset to Default reapplies the full baseline, api key included', () => {
|
||||
settingsStore.initialize();
|
||||
settingsStore.updateConfig('theme', 'light');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue