mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-11 22:52:54 +00:00
refac
This commit is contained in:
parent
675b9839f1
commit
98a920168e
4 changed files with 172 additions and 46 deletions
|
|
@ -4,12 +4,18 @@ from __future__ import annotations
|
|||
|
||||
import datetime
|
||||
import time
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
from open_webui.env import DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL
|
||||
from open_webui.internal.db import Base, JSONField, get_async_db_context
|
||||
from open_webui.utils.misc import throttle
|
||||
from open_webui.utils.validate import validate_profile_image_url
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
field_validator,
|
||||
model_validator,
|
||||
)
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
BigInteger,
|
||||
|
|
@ -36,6 +42,97 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
####################
|
||||
|
||||
|
||||
class InterfaceTitleSettings(BaseModel):
|
||||
model_config = ConfigDict(extra='forbid')
|
||||
|
||||
auto: bool | None = None
|
||||
|
||||
|
||||
class InterfaceImageCompressionSize(BaseModel):
|
||||
model_config = ConfigDict(extra='forbid')
|
||||
|
||||
width: int | float | Literal[''] | None = None
|
||||
height: int | float | Literal[''] | None = None
|
||||
|
||||
|
||||
class InterfaceFloatingActionButton(BaseModel):
|
||||
model_config = ConfigDict(extra='forbid')
|
||||
|
||||
id: str
|
||||
label: str
|
||||
input: bool
|
||||
prompt: str
|
||||
|
||||
|
||||
class InterfaceSettings(BaseModel):
|
||||
"""Fields owned by the Interface settings panel; not the entire user UI dict."""
|
||||
|
||||
model_config = ConfigDict(extra='forbid')
|
||||
|
||||
autoTags: bool | None = None
|
||||
autoFollowUps: bool | None = None
|
||||
highContrastMode: bool | None = None
|
||||
detectArtifacts: bool | None = None
|
||||
responseAutoCopy: bool | None = None
|
||||
showUsername: bool | None = None
|
||||
showUpdateToast: bool | None = None
|
||||
showChangelog: bool | None = None
|
||||
showEmojiInCall: bool | None = None
|
||||
voiceInterruption: bool | None = None
|
||||
displayMultiModelResponsesInTabs: bool | None = None
|
||||
chatFadeStreamingText: bool | None = None
|
||||
richTextInput: bool | None = None
|
||||
showFormattingToolbar: bool | None = None
|
||||
insertPromptAsRichText: bool | None = None
|
||||
promptAutocomplete: bool | None = None
|
||||
insertSuggestionPrompt: bool | None = None
|
||||
keepFollowUpPrompts: bool | None = None
|
||||
insertFollowUpPrompt: bool | None = None
|
||||
regenerateMenu: bool | None = None
|
||||
enableMessageQueue: bool | None = None
|
||||
largeTextAsFile: bool | None = None
|
||||
copyFormatted: bool | None = None
|
||||
collapseCodeBlocks: bool | None = None
|
||||
renderMarkdownInUserMessages: bool | None = None
|
||||
renderMarkdownInAssistantMessages: bool | None = None
|
||||
expandDetails: bool | None = None
|
||||
chatHoverPreview: bool | None = None
|
||||
renderMarkdownInPreviews: bool | None = None
|
||||
chatBubble: bool | None = None
|
||||
widescreenMode: bool | None = None
|
||||
splitLargeChunks: bool | None = None
|
||||
scrollOnBranchChange: bool | None = None
|
||||
scrollOnResponseGeneration: bool | None = None
|
||||
showFilesOnTerminalSelect: bool | None = None
|
||||
temporaryChatByDefault: bool | None = None
|
||||
userLocation: bool | None = None
|
||||
showChatTitleInTab: bool | None = None
|
||||
iframeSandboxAllowScripts: bool | None = None
|
||||
iframeSandboxAllowSameOrigin: bool | None = None
|
||||
iframeSandboxAllowForms: bool | None = None
|
||||
iframeSandboxAllowDownloads: bool | None = None
|
||||
terminalPreviewAllowSameOrigin: bool | None = None
|
||||
stylizedPdfExport: bool | None = None
|
||||
hapticFeedback: bool | None = None
|
||||
ctrlEnterToSend: bool | None = None
|
||||
showFloatingActionButtons: bool | None = None
|
||||
imageCompression: bool | None = None
|
||||
imageCompressionInChannels: bool | None = None
|
||||
|
||||
landingPageMode: Literal['', 'chat'] | None = None
|
||||
chatDirection: Literal['LTR', 'RTL', 'auto'] | None = None
|
||||
terminalFileDisplay: Literal['sidebar', 'inline'] | None = None
|
||||
defaultUploadContext: Literal['full', 'focused'] | None = None
|
||||
webSearch: Literal['always'] | None = None
|
||||
models: list[str] | None = None
|
||||
backgroundImageUrl: str | None = None
|
||||
fontFamily: str | None = None
|
||||
textScale: float | None = None
|
||||
title: InterfaceTitleSettings | None = None
|
||||
imageCompressionSize: InterfaceImageCompressionSize | None = None
|
||||
floatingActionButtons: list[InterfaceFloatingActionButton] | None = None
|
||||
|
||||
|
||||
class UserSettings(BaseModel):
|
||||
ui: dict | None = {}
|
||||
model_config = ConfigDict(extra='allow')
|
||||
|
|
@ -729,7 +826,18 @@ class UsersTable:
|
|||
if not user:
|
||||
return None
|
||||
user_settings = dict(user.settings or {})
|
||||
updated = dict(updated)
|
||||
ui_settings = updated.pop('ui', None)
|
||||
user_settings.update(updated)
|
||||
if ui_settings is not None:
|
||||
# UI updates are field-level patches: omission keeps a value; null resets it.
|
||||
current_ui_settings = dict(user_settings.get('ui') or {})
|
||||
for key, value in ui_settings.items():
|
||||
if value is None:
|
||||
current_ui_settings.pop(key, None)
|
||||
else:
|
||||
current_ui_settings[key] = value
|
||||
user_settings['ui'] = current_ui_settings
|
||||
user.settings = user_settings
|
||||
await session.commit()
|
||||
return UserModel.model_validate(user)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from open_webui.models.chats import Chats
|
|||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.oauth_sessions import OAuthSessions
|
||||
from open_webui.models.users import (
|
||||
InterfaceSettings,
|
||||
UserGroupIdsListResponse,
|
||||
UserGroupIdsModel,
|
||||
UserInfoListResponse,
|
||||
|
|
@ -68,23 +69,6 @@ def merge_user_ui_settings(defaults: dict, settings: dict) -> dict:
|
|||
return merged
|
||||
|
||||
|
||||
def strip_default_interface_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_interface_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
|
||||
|
|
@ -502,16 +486,37 @@ async def update_user_settings_by_session_user(
|
|||
user=Depends(get_verified_user),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'settings.interface', await Config.get('user.permissions')
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
updated_user_settings = form_data.model_dump(exclude_unset=True)
|
||||
ui_settings = updated_user_settings.get('ui')
|
||||
|
||||
if isinstance(ui_settings, dict):
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'settings.interface', await Config.get('user.permissions'), db=db
|
||||
):
|
||||
# Omitted fields are unchanged, so unauthorized Interface fields can be discarded.
|
||||
for key in InterfaceSettings.model_fields:
|
||||
ui_settings.pop(key, None)
|
||||
|
||||
if (
|
||||
user.role != 'admin'
|
||||
and 'system' in ui_settings
|
||||
and (
|
||||
not await has_permission(user.id, 'chat.controls', await Config.get('user.permissions'), db=db)
|
||||
or not await has_permission(user.id, 'chat.system_prompt', await Config.get('user.permissions'), db=db)
|
||||
)
|
||||
):
|
||||
ui_settings.pop('system', None)
|
||||
|
||||
if (
|
||||
user.role != 'admin'
|
||||
and 'params' in ui_settings
|
||||
and (
|
||||
not await has_permission(user.id, 'chat.controls', await Config.get('user.permissions'), db=db)
|
||||
or not await has_permission(user.id, 'chat.params', await Config.get('user.permissions'), db=db)
|
||||
)
|
||||
):
|
||||
ui_settings.pop('params', None)
|
||||
|
||||
if (
|
||||
user.role != 'admin'
|
||||
and ui_settings is not None
|
||||
|
|
@ -522,8 +527,7 @@ async def update_user_settings_by_session_user(
|
|||
await Config.get('user.permissions'),
|
||||
)
|
||||
):
|
||||
# If the user is not an admin and does not have permission to use tool servers, remove the key
|
||||
updated_user_settings['ui'].pop('toolServers', None)
|
||||
ui_settings.pop('toolServers', None)
|
||||
|
||||
ui_notifications = ui_settings.get('notifications') if isinstance(ui_settings, dict) else None
|
||||
if (
|
||||
|
|
@ -542,11 +546,6 @@ async def update_user_settings_by_session_user(
|
|||
if isinstance(ui_notifications, dict):
|
||||
ui_notifications.pop('webhook_url', None)
|
||||
|
||||
default_interface_settings = await Config.get('ui.default_interface_settings')
|
||||
ui_settings = updated_user_settings.get('ui')
|
||||
if isinstance(default_interface_settings, dict) and isinstance(ui_settings, dict):
|
||||
updated_user_settings['ui'] = strip_default_interface_settings(default_interface_settings, ui_settings)
|
||||
|
||||
user = await Users.update_user_settings_by_id(user.id, updated_user_settings, db=db)
|
||||
if user:
|
||||
await publish_event(
|
||||
|
|
|
|||
|
|
@ -66,10 +66,21 @@
|
|||
keep_alive: null
|
||||
};
|
||||
|
||||
$: canEditSystemPrompt =
|
||||
$user?.role === 'admin' ||
|
||||
(($user?.permissions.chat?.controls ?? true) &&
|
||||
($user?.permissions.chat?.system_prompt ?? true));
|
||||
$: canEditParams =
|
||||
$user?.role === 'admin' ||
|
||||
(($user?.permissions.chat?.controls ?? true) && ($user?.permissions.chat?.params ?? true));
|
||||
|
||||
const saveHandler = async () => {
|
||||
saveSettings({
|
||||
system: system !== '' ? system : undefined,
|
||||
params: {
|
||||
const updated: Record<string, any> = {};
|
||||
if (canEditSystemPrompt) {
|
||||
updated.system = system !== '' ? system : null;
|
||||
}
|
||||
if (canEditParams) {
|
||||
updated.params = {
|
||||
stream_response: params.stream_response !== null ? params.stream_response : undefined,
|
||||
stream_delta_chunk_size:
|
||||
params.stream_delta_chunk_size !== null ? params.stream_delta_chunk_size : undefined,
|
||||
|
|
@ -105,9 +116,14 @@
|
|||
...(params.custom_params && Object.keys(params.custom_params).length > 0
|
||||
? { custom_params: params.custom_params }
|
||||
: {})
|
||||
}
|
||||
});
|
||||
dispatch('save');
|
||||
};
|
||||
}
|
||||
try {
|
||||
await saveSettings(updated);
|
||||
dispatch('save');
|
||||
} catch {
|
||||
// The settings modal displays the save error; do not report success.
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
|
|
@ -255,7 +271,7 @@
|
|||
{/if}
|
||||
</UserSettingSection>
|
||||
|
||||
{#if $user?.role === 'admin' || (($user?.permissions.chat?.controls ?? true) && ($user?.permissions.chat?.system_prompt ?? true))}
|
||||
{#if canEditSystemPrompt}
|
||||
<UserSettingSection title={$i18n.t('System Prompt')}>
|
||||
<UserSettingField description={$i18n.t('Set the default system prompt for new chats.')}>
|
||||
<Textarea
|
||||
|
|
@ -268,7 +284,7 @@
|
|||
</UserSettingSection>
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin' || (($user?.permissions.chat?.controls ?? true) && ($user?.permissions.chat?.params ?? true))}
|
||||
{#if canEditParams}
|
||||
<UserSettingSection title={$i18n.t('Advanced Parameters')}>
|
||||
<UserSettingRow description={$i18n.t('Show or hide custom generation parameters.')}>
|
||||
<span slot="label">{$i18n.t('Model parameters')}</span>
|
||||
|
|
|
|||
|
|
@ -857,15 +857,18 @@
|
|||
};
|
||||
|
||||
const saveSettings = async (updated: Record<string, any>) => {
|
||||
console.log(updated);
|
||||
await settings.set({ ...$settings, ...updated });
|
||||
await models.set(await getModels());
|
||||
const saved = await updateUserSettings(localStorage.token, { ui: $settings });
|
||||
const saved = await updateUserSettings(localStorage.token, {
|
||||
ui: updated
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
throw error;
|
||||
});
|
||||
personalUiSettings =
|
||||
saved?.ui && typeof saved.ui === 'object' && !Array.isArray(saved.ui) ? saved.ui : {};
|
||||
await settings.set(
|
||||
mergeUiSettings($config?.ui?.default_interface_settings ?? {}, personalUiSettings)
|
||||
);
|
||||
await models.set(await getModels());
|
||||
};
|
||||
|
||||
const getModels = async () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue