diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index c286152abd..fd2ad14602 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -676,9 +676,19 @@ class InterfaceDefaultsForm(BaseModel): @field_validator("chatDirection") @classmethod def validate_chat_direction(cls, value): - if value is not None and value not in ("auto", "ltr", "rtl"): - raise ValueError("chatDirection must be 'auto', 'ltr', or 'rtl'") - return value + # The user-side store is typed 'LTR' | 'RTL' | 'auto' and the + # Interface settings toggle cycles uppercase values, so accept any + # casing and normalize to the shape the frontend expects. Rejecting + # the uppercase values outright (as the validator did before) made + # the modal fail with a 422 on every LTR/RTL save. + if value is None: + return value + normalized = value.lower() + if normalized == "auto": + return "auto" + if normalized in ("ltr", "rtl"): + return normalized.upper() + raise ValueError("chatDirection must be 'auto', 'LTR', or 'RTL'") @field_validator("textScale") @classmethod diff --git a/src/lib/apis/configs/index.ts b/src/lib/apis/configs/index.ts index 7cbdad0431..df16b033d2 100644 --- a/src/lib/apis/configs/index.ts +++ b/src/lib/apis/configs/index.ts @@ -664,7 +664,12 @@ export const getInterfaceDefaults = async (token: string) => { }) .catch((err) => { console.error(err); - error = err.detail; + // Fall back through detail / message / stringification so a + // network TypeError or non-JSON response still produces a truthy + // error — otherwise the function silently returned null and the + // caller couldn't tell a failed fetch from an empty-defaults + // response. + error = err?.detail ?? err?.message ?? (typeof err === 'string' ? err : 'Request failed'); return null; }); @@ -692,7 +697,7 @@ export const setInterfaceDefaults = async (token: string, defaults: object) => { }) .catch((err) => { console.error(err); - error = err.detail; + error = err?.detail ?? err?.message ?? (typeof err === 'string' ? err : 'Request failed'); return null; }); diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts index b599f0b7fc..23e8eca5df 100644 --- a/src/lib/apis/users/index.ts +++ b/src/lib/apis/users/index.ts @@ -567,7 +567,11 @@ export const resetAllUsersInterfaceSettings = async (token: string) => { }) .catch((err) => { console.error(err); - error = err.detail; + // Fall back through detail / message / stringification so a + // network TypeError or non-JSON response still produces a truthy + // error — otherwise the function silently returned null and the + // caller could mistake a failed reset for a silent success. + error = err?.detail ?? err?.message ?? (typeof err === 'string' ? err : 'Request failed'); return null; }); diff --git a/src/lib/components/admin/Settings/Interface.svelte b/src/lib/components/admin/Settings/Interface.svelte index 204415910c..8bec5c8606 100644 --- a/src/lib/components/admin/Settings/Interface.svelte +++ b/src/lib/components/admin/Settings/Interface.svelte @@ -114,6 +114,42 @@ }} >