From b5f86e6a433b519e30a5a6f1b0a5bf985939c9a8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 10 Aug 2026 19:08:06 -0600 Subject: [PATCH] refac --- backend/open_webui/config.py | 10 +++---- backend/open_webui/main.py | 2 ++ backend/open_webui/models/config.py | 2 +- backend/open_webui/routers/auths.py | 6 ++--- backend/open_webui/routers/users.py | 27 ++++++++++++++++++- .../components/admin/Settings/General.svelte | 16 +++++------ .../common/InterfaceSettings.svelte | 11 ++++++-- src/lib/stores/index.ts | 2 +- 8 files changed, 55 insertions(+), 21 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 5a08a3b60b..de5963c12d 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1704,12 +1704,12 @@ DEFAULT_MODEL_PARAMS = default_model_params try: - default_user_ui_settings = JSONCodec.loads(os.getenv('DEFAULT_USER_UI_SETTINGS', '{}')) + default_ui_settings = JSONCodec.loads(os.getenv('DEFAULT_UI_SETTINGS', '{}')) except Exception as e: - log.exception(f'Error loading DEFAULT_USER_UI_SETTINGS: {e}') - default_user_ui_settings = {} + log.exception(f'Error loading DEFAULT_UI_SETTINGS: {e}') + default_ui_settings = {} -DEFAULT_USER_UI_SETTINGS = default_user_ui_settings if isinstance(default_user_ui_settings, dict) else {} +DEFAULT_UI_SETTINGS = default_ui_settings if isinstance(default_ui_settings, dict) else {} DEFAULT_USER_ROLE = os.getenv('DEFAULT_USER_ROLE', 'pending') @@ -3064,7 +3064,7 @@ DEFAULT_CONFIG = { 'ui.default_locale': DEFAULT_LOCALE, 'ui.default_models': DEFAULT_MODELS, 'ui.default_pinned_models': DEFAULT_PINNED_MODELS, - 'ui.default_user_settings': DEFAULT_USER_UI_SETTINGS, + 'ui.default_settings': DEFAULT_UI_SETTINGS, 'ui.prompt_suggestions': DEFAULT_PROMPT_SUGGESTIONS, 'ui.model_order_list': MODEL_ORDER_LIST, 'models.default_metadata': DEFAULT_MODEL_METADATA, diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index f5c182eb3f..8e25ac5dd0 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -2142,6 +2142,7 @@ async def get_app_config(request: Request): 'memories.enable', 'ui.default_models', 'ui.default_pinned_models', + 'ui.default_settings', 'ui.prompt_suggestions', 'code_execution.engine', 'code_interpreter.engine', @@ -2272,6 +2273,7 @@ async def get_app_config(request: Request): 'sharepoint_tenant_id': ONEDRIVE_SHAREPOINT_TENANT_ID, }, 'ui': { + 'default_settings': config.get('ui.default_settings'), 'pending_user_overlay_title': config.get('ui.pending_user_overlay_title'), 'pending_user_overlay_content': config.get('ui.pending_user_overlay_content'), 'response_watermark': config.get('ui.watermark'), diff --git a/backend/open_webui/models/config.py b/backend/open_webui/models/config.py index b7dd54ae17..29d23fee8b 100644 --- a/backend/open_webui/models/config.py +++ b/backend/open_webui/models/config.py @@ -32,7 +32,7 @@ DICT_CONFIG_KEY_ALIASES = { 'audio.tts.openai.params': ('AUDIO_TTS_OPENAI_PARAMS',), 'models.default_metadata': ('DEFAULT_MODEL_METADATA',), 'models.default_params': ('DEFAULT_MODEL_PARAMS',), - 'ui.default_user_settings': ('DEFAULT_USER_UI_SETTINGS',), + 'ui.default_settings': ('DEFAULT_UI_SETTINGS',), 'user.permissions': ('USER_PERMISSIONS',), } DICT_CONFIG_KEYS = tuple(DICT_CONFIG_KEY_ALIASES) diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 1256197da4..82d0eb8c38 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -111,7 +111,7 @@ ADMIN_CONFIG_KEYS = { 'API_KEYS_ALLOWED_ENDPOINTS': 'auth.api_key.allowed_endpoints', 'DEFAULT_USER_ROLE': 'ui.default_user_role', 'DEFAULT_GROUP_ID': 'ui.default_group_id', - 'DEFAULT_USER_UI_SETTINGS': 'ui.default_user_settings', + 'DEFAULT_UI_SETTINGS': 'ui.default_settings', 'JWT_EXPIRES_IN': 'auth.jwt_expiry', 'ENABLE_COMMUNITY_SHARING': 'ui.enable_community_sharing', 'ENABLE_MESSAGE_RATING': 'ui.enable_message_rating', @@ -1197,7 +1197,7 @@ class AdminConfig(BaseModel): API_KEYS_ALLOWED_ENDPOINTS: str DEFAULT_USER_ROLE: str DEFAULT_GROUP_ID: str - DEFAULT_USER_UI_SETTINGS: dict | None = None + DEFAULT_UI_SETTINGS: dict | None = None JWT_EXPIRES_IN: str ENABLE_COMMUNITY_SHARING: bool ENABLE_MESSAGE_RATING: bool @@ -1222,7 +1222,7 @@ class AdminConfig(BaseModel): @router.post('/admin/config') async def update_admin_config(request: Request, form_data: AdminConfig, user=Depends(get_admin_user)): updates = config_updates(form_data.model_dump(), ADMIN_CONFIG_KEYS) - updates['ui.default_user_settings'] = form_data.DEFAULT_USER_UI_SETTINGS or {} + updates['ui.default_settings'] = form_data.DEFAULT_UI_SETTINGS or {} updates['folders.max_file_count'] = int(form_data.FOLDER_MAX_FILE_COUNT) if form_data.FOLDER_MAX_FILE_COUNT else '' updates['automations.max_count'] = int(form_data.AUTOMATION_MAX_COUNT) if form_data.AUTOMATION_MAX_COUNT else '' updates['automations.min_interval'] = ( diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index bd63a40bb6..46f7f7efa8 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -56,6 +56,9 @@ router = APIRouter() def merge_user_ui_settings(defaults: dict, settings: dict) -> dict: merged = dict(defaults) for key, value in settings.items(): + if value is None: + continue + default_value = merged.get(key) merged[key] = ( merge_user_ui_settings(default_value, value) @@ -65,6 +68,23 @@ def merge_user_ui_settings(defaults: dict, settings: dict) -> dict: return merged +def strip_default_ui_settings(defaults: dict, settings: dict) -> dict: + stripped = {} + for key, value in settings.items(): + if value is None: + continue + + default_value = defaults.get(key) + if isinstance(default_value, dict) and isinstance(value, dict): + nested = strip_default_ui_settings(default_value, value) + if nested: + stripped[key] = nested + elif value != default_value: + stripped[key] = value + + return stripped + + ############################ # GetUsers # A house is only as strong as its care for the least of @@ -452,7 +472,7 @@ async def get_user_settings_by_session_user( user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) ): # user already fetched by get_verified_user — no need to refetch - default_ui_settings = await Config.get('ui.default_user_settings') + default_ui_settings = await Config.get('ui.default_settings') if not isinstance(default_ui_settings, dict) or not default_ui_settings: return user.settings @@ -515,6 +535,11 @@ async def update_user_settings_by_session_user( if isinstance(ui_notifications, dict): ui_notifications.pop('webhook_url', None) + default_ui_settings = await Config.get('ui.default_settings') + ui_settings = updated_user_settings.get('ui') + if isinstance(default_ui_settings, dict) and isinstance(ui_settings, dict): + updated_user_settings['ui'] = strip_default_ui_settings(default_ui_settings, ui_settings) + user = await Users.update_user_settings_by_id(user.id, updated_user_settings, db=db) if user: await publish_event( diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index 1cb55bca8d..a858c2af7a 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -65,8 +65,13 @@ defaultUserUiSettings = { ...defaultUserUiSettings, ...updated }; }; + const getDefaultUserUiSettings = () => { + const value = adminConfig?.DEFAULT_UI_SETTINGS; + return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; + }; + const updateHandler = async () => { - adminConfig.DEFAULT_USER_UI_SETTINGS = defaultUserUiSettings; + adminConfig.DEFAULT_UI_SETTINGS = defaultUserUiSettings; const res = await updateAdminConfig(localStorage.token, adminConfig); @@ -83,12 +88,7 @@ onMount(async () => { adminConfig = await getAdminConfig(localStorage.token); - defaultUserUiSettings = - adminConfig?.DEFAULT_USER_UI_SETTINGS && - typeof adminConfig.DEFAULT_USER_UI_SETTINGS === 'object' && - !Array.isArray(adminConfig.DEFAULT_USER_UI_SETTINGS) - ? adminConfig.DEFAULT_USER_UI_SETTINGS - : {}; + defaultUserUiSettings = getDefaultUserUiSettings(); banners = [...$_banners]; }); @@ -396,7 +396,7 @@
{$i18n.t('Default Interface Settings')}
{$i18n.t( - 'Set the interface values new users start with. User changes still override these defaults.' + 'Set system-wide interface defaults for every account. Personal settings override these defaults.' )}
diff --git a/src/lib/components/common/InterfaceSettings.svelte b/src/lib/components/common/InterfaceSettings.svelte index 9b54a01fa2..8e9586ecc4 100644 --- a/src/lib/components/common/InterfaceSettings.svelte +++ b/src/lib/components/common/InterfaceSettings.svelte @@ -19,6 +19,7 @@ $: currentSettings = (settingsValue ?? $settings ?? {}) as Record; $: externalSettings = settingsValue !== null; + $: defaultSettings = ($config?.ui?.default_settings ?? {}) as Record; let backgroundImageUrl: string | null = null; let inputFiles: FileList | null = null; @@ -117,6 +118,11 @@ const actionButtonClass = 'text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white'; + const matchesDefaultSetting = (key: string, value: unknown) => + !externalSettings && + Object.prototype.hasOwnProperty.call(defaultSettings, key) && + JSON.stringify(defaultSettings[key]) === JSON.stringify(value); + const toggleLandingPageMode = async () => { landingPageMode = landingPageMode === '' ? 'chat' : ''; saveSettings({ landingPageMode: landingPageMode }); @@ -309,6 +315,7 @@ lastSettingsValue = settingsValue; init(); } + $: textScaleUsesDefault = textScale === null || matchesDefaultSetting('textScale', textScale); onMount(async () => { init(); @@ -376,13 +383,13 @@ on:click={() => { if (textScale === null) { textScale = 1; - } else { + } else if (!textScaleUsesDefault) { textScale = null; setTextScaleHandler(1); } }} > - {#if textScale === null} + {#if textScaleUsesDefault} {$i18n.t('Default')} {:else} {textScale}x diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index f874055d33..65299774d0 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -342,7 +342,7 @@ type Config = { auto_redirect?: boolean; }; ui?: { - default_user_settings?: Record; + default_settings?: Record; pending_user_overlay_title?: string; pending_user_overlay_content?: string; response_watermark?: string;