diff --git a/src/lib/components/admin/Settings/Interface.svelte b/src/lib/components/admin/Settings/Interface.svelte index 0a4d0a6891..204415910c 100644 --- a/src/lib/components/admin/Settings/Interface.svelte +++ b/src/lib/components/admin/Settings/Interface.svelte @@ -10,9 +10,16 @@ import Switch from '$lib/components/common/Switch.svelte'; import Textarea from '$lib/components/common/Textarea.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; + import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; + import InterfaceDefaultsModal from './Interface/InterfaceDefaultsModal.svelte'; import { resetAllUsersInterfaceSettings } from '$lib/apis/users'; + // Toggles for the admin-defaults modal and the "reset all users" confirm + // dialog rendered at the bottom of this panel. + let showInterfaceDefaultsModal = false; + let showResetConfirmDialog = false; + const dispatch = createEventDispatcher(); const i18n = getContext('i18n'); diff --git a/src/lib/components/admin/Settings/Interface/InterfaceDefaultsModal.svelte b/src/lib/components/admin/Settings/Interface/InterfaceDefaultsModal.svelte index cdefeac1bb..8c9f1dbf81 100644 --- a/src/lib/components/admin/Settings/Interface/InterfaceDefaultsModal.svelte +++ b/src/lib/components/admin/Settings/Interface/InterfaceDefaultsModal.svelte @@ -33,16 +33,15 @@ } }; - // Prepare settings for backend - normalize chatDirection and filter nulls + // Prepare settings for backend — strip nulls so absent keys fall through + // to the user's own setting via deep-merge on load. Do NOT lowercase + // chatDirection: the user-side store is typed 'LTR' | 'RTL' | 'auto' + // and the toggle/render logic in chat/Settings/Interface.svelte matches + // against the uppercase values. Lowercasing here made admin defaults + // silently render as "Auto" because neither 'ltr' nor 'rtl' matched. const prepareForBackend = (settings: any): object => { - const result = { ...settings }; - - if (result.chatDirection) { - result.chatDirection = result.chatDirection.toLowerCase(); - } - return Object.fromEntries( - Object.entries(result).filter(([_, v]) => v !== undefined && v !== null) + Object.entries({ ...settings }).filter(([_, v]) => v !== undefined && v !== null) ); }; @@ -89,12 +88,15 @@ {#if !loading} -
{ - submitHandler(); - }} - > + +
@@ -104,7 +106,8 @@ class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {saving ? ' cursor-not-allowed' : ''}" - type="submit" + type="button" + on:click={submitHandler} disabled={saving} > {$i18n.t('Save')} @@ -116,7 +119,7 @@ {/if}
-
+ {:else}
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 9617f4e6d4..4d26d1ff4d 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1840,6 +1840,7 @@ export const formatSkillName = (name) => { return name.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); }; +/** * Open the file browser panel to display a specific file. * Used by both the direct tool execution path (client-side) and the * backend event path (server-side) so behaviour is consistent. diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index edebe8b14f..480f01f2c4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -916,19 +916,21 @@ $socket?.on('events', chatEventHandler); $socket?.on('events:channel', channelEventHandler); - const userSettings = await getUserSettings(localStorage.token); + // Fetch user settings and admin-configured interface defaults in + // parallel — both are independent network calls on the hot + // authenticated-load path, and serializing them added + // avoidable round-trip latency. Defaults fall back to {} if + // the endpoint is unavailable so user settings still apply. + const [userSettings, adminDefaults] = await Promise.all([ + getUserSettings(localStorage.token), + getInterfaceDefaults(localStorage.token).catch((e) => { + console.warn('Failed to load admin interface defaults:', e); + return {}; + }) + ]); - // Fetch admin-configured interface defaults and merge with user settings - let effectiveSettings = {}; - try { - const adminDefaults = await getInterfaceDefaults(localStorage.token); - const userUI = userSettings?.ui ?? {}; - // Admin defaults are base, user settings override - effectiveSettings = deepMerge(adminDefaults, userUI); - } catch (e) { - // Fall back to user settings if admin defaults unavailable - effectiveSettings = userSettings?.ui ?? {}; - } + // Admin defaults are base, user settings override. + const effectiveSettings = deepMerge(adminDefaults ?? {}, userSettings?.ui ?? {}); if (userSettings) { settings.set(effectiveSettings);