mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-07 08:27:05 +00:00
fix: restore JSDoc marker, wire modal bindings, align chatDirection, drop nested form, parallelize load
- Restore the /** opener above displayFileHandler in utils/index.ts that was dropped during the earlier merge; the orphaned comment body made the file fail TypeScript parsing and broke the frontend build. - admin Settings/Interface.svelte now imports InterfaceDefaultsModal and ConfirmDialog and declares the showInterfaceDefaultsModal / showResetConfirmDialog flags that were already being bound at the bottom of the template, resolving the unresolved-identifier compile errors. - InterfaceDefaultsModal no longer lowercases chatDirection before saving. The user-side store is typed 'LTR' | 'RTL' | 'auto' and the toggle/render logic matches those uppercase values; lowercasing on the admin side made every saved RTL/LTR default silently render as "Auto" for end users. - InterfaceDefaultsModal drops the outer <form> wrapper. InterfaceSettings already renders its own <form> and nesting forms is invalid HTML; the Save button is now a plain type="button" with on:click=submitHandler, which keeps the click-to-save behaviour without the nested-form event routing surprises. - +layout.svelte fetches user settings and admin defaults in parallel via Promise.all, with an inline .catch fallback on the defaults call so the endpoint being unavailable still lets user settings apply. This removes an avoidable serial round-trip on every authenticated load.
This commit is contained in:
parent
4497226b94
commit
6712c83cf6
4 changed files with 41 additions and 28 deletions
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
</div>
|
||||
|
||||
{#if !loading}
|
||||
<form
|
||||
class="flex flex-col w-full"
|
||||
on:submit|preventDefault={() => {
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
<!--
|
||||
No outer <form> wrapper: InterfaceSettings already renders its
|
||||
own <form>, and nesting forms is invalid HTML and produces
|
||||
unpredictable submit/event routing across browsers. The
|
||||
InterfaceSettings component feeds changes back through the
|
||||
saveAdminSettings prop, so we just need a Save button that
|
||||
pushes the accumulated adminDefaults to the backend.
|
||||
-->
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="interface-defaults-wrapper">
|
||||
<InterfaceSettings initialSettings={adminDefaults} saveSettings={saveAdminSettings} />
|
||||
</div>
|
||||
|
|
@ -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}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex justify-center items-center py-8">
|
||||
<Spinner className="size-5" />
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue