feat: response watermark

This commit is contained in:
Timothy Jaeryang Baek 2025-05-14 23:53:28 +04:00
parent b143c71da2
commit 0a8cecfbfa
7 changed files with 39 additions and 10 deletions
backend/open_webui
src/lib/components

View File

@ -1002,6 +1002,13 @@ PENDING_USER_OVERLAY_CONTENT = PersistentConfig(
) )
RESPONSE_WATERMARK = PersistentConfig(
"RESPONSE_WATERMARK",
"ui.watermark",
os.environ.get("RESPONSE_WATERMARK", ""),
)
USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS = ( USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS = (
os.environ.get("USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS", "False").lower() os.environ.get("USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS", "False").lower()
== "true" == "true"

View File

@ -334,6 +334,7 @@ from open_webui.config import (
DEFAULT_LOCALE, DEFAULT_LOCALE,
OAUTH_PROVIDERS, OAUTH_PROVIDERS,
WEBUI_URL, WEBUI_URL,
RESPONSE_WATERMARK,
# Admin # Admin
ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_CHAT_ACCESS,
ENABLE_ADMIN_EXPORT, ENABLE_ADMIN_EXPORT,
@ -580,9 +581,12 @@ app.state.config.ADMIN_EMAIL = ADMIN_EMAIL
app.state.config.DEFAULT_MODELS = DEFAULT_MODELS app.state.config.DEFAULT_MODELS = DEFAULT_MODELS
app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
app.state.config.PENDING_USER_OVERLAY_CONTENT = PENDING_USER_OVERLAY_CONTENT app.state.config.PENDING_USER_OVERLAY_CONTENT = PENDING_USER_OVERLAY_CONTENT
app.state.config.PENDING_USER_OVERLAY_TITLE = PENDING_USER_OVERLAY_TITLE app.state.config.PENDING_USER_OVERLAY_TITLE = PENDING_USER_OVERLAY_TITLE
app.state.config.RESPONSE_WATERMARK = RESPONSE_WATERMARK
app.state.config.USER_PERMISSIONS = USER_PERMISSIONS app.state.config.USER_PERMISSIONS = USER_PERMISSIONS
app.state.config.WEBHOOK_URL = WEBHOOK_URL app.state.config.WEBHOOK_URL = WEBHOOK_URL
app.state.config.BANNERS = WEBUI_BANNERS app.state.config.BANNERS = WEBUI_BANNERS
@ -1413,6 +1417,7 @@ async def get_app_config(request: Request):
"ui": { "ui": {
"pending_user_overlay_title": app.state.config.PENDING_USER_OVERLAY_TITLE, "pending_user_overlay_title": app.state.config.PENDING_USER_OVERLAY_TITLE,
"pending_user_overlay_content": app.state.config.PENDING_USER_OVERLAY_CONTENT, "pending_user_overlay_content": app.state.config.PENDING_USER_OVERLAY_CONTENT,
"response_watermark": app.state.config.RESPONSE_WATERMARK,
}, },
"license_metadata": app.state.LICENSE_METADATA, "license_metadata": app.state.LICENSE_METADATA,
**( **(

View File

@ -187,9 +187,7 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
LDAP_USE_TLS = request.app.state.config.LDAP_USE_TLS LDAP_USE_TLS = request.app.state.config.LDAP_USE_TLS
LDAP_CA_CERT_FILE = request.app.state.config.LDAP_CA_CERT_FILE LDAP_CA_CERT_FILE = request.app.state.config.LDAP_CA_CERT_FILE
LDAP_VALIDATE_CERT = ( LDAP_VALIDATE_CERT = (
CERT_REQUIRED CERT_REQUIRED if request.app.state.config.LDAP_VALIDATE_CERT else CERT_NONE
if request.app.state.config.LDAP_VALIDATE_CERT
else CERT_NONE
) )
LDAP_CIPHERS = ( LDAP_CIPHERS = (
request.app.state.config.LDAP_CIPHERS request.app.state.config.LDAP_CIPHERS
@ -703,6 +701,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS, "ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
"PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE, "PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE,
"PENDING_USER_OVERLAY_CONTENT": request.app.state.config.PENDING_USER_OVERLAY_CONTENT, "PENDING_USER_OVERLAY_CONTENT": request.app.state.config.PENDING_USER_OVERLAY_CONTENT,
"RESPONSE_WATERMARK": request.app.state.config.RESPONSE_WATERMARK,
} }
@ -722,6 +721,7 @@ class AdminConfig(BaseModel):
ENABLE_USER_WEBHOOKS: bool ENABLE_USER_WEBHOOKS: bool
PENDING_USER_OVERLAY_TITLE: Optional[str] = None PENDING_USER_OVERLAY_TITLE: Optional[str] = None
PENDING_USER_OVERLAY_CONTENT: Optional[str] = None PENDING_USER_OVERLAY_CONTENT: Optional[str] = None
RESPONSE_WATERMARK: Optional[str] = None
@router.post("/admin/config") @router.post("/admin/config")
@ -766,6 +766,8 @@ async def update_admin_config(
form_data.PENDING_USER_OVERLAY_CONTENT form_data.PENDING_USER_OVERLAY_CONTENT
) )
request.app.state.config.RESPONSE_WATERMARK = form_data.RESPONSE_WATERMARK
return { return {
"SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS, "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS,
"WEBUI_URL": request.app.state.config.WEBUI_URL, "WEBUI_URL": request.app.state.config.WEBUI_URL,
@ -782,6 +784,7 @@ async def update_admin_config(
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS, "ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
"PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE, "PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE,
"PENDING_USER_OVERLAY_CONTENT": request.app.state.config.PENDING_USER_OVERLAY_CONTENT, "PENDING_USER_OVERLAY_CONTENT": request.app.state.config.PENDING_USER_OVERLAY_CONTENT,
"RESPONSE_WATERMARK": request.app.state.config.RESPONSE_WATERMARK,
} }

View File

@ -306,8 +306,8 @@
<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} /> <Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} />
</div> </div>
<div class="mb-3.5"> <div class="mb-2.5">
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-2">
{$i18n.t('Pending User Overlay Title')} {$i18n.t('Pending User Overlay Title')}
</div> </div>
<Textarea <Textarea
@ -319,8 +319,8 @@
/> />
</div> </div>
<div class="mb-3.5"> <div class="mb-2.5">
<div class=" self-center text-xs font-medium mb-1"> <div class=" self-center text-xs font-medium mb-2">
{$i18n.t('Pending User Overlay Content')} {$i18n.t('Pending User Overlay Content')}
</div> </div>
<Textarea <Textarea
@ -658,6 +658,16 @@
<Switch bind:state={adminConfig.ENABLE_USER_WEBHOOKS} /> <Switch bind:state={adminConfig.ENABLE_USER_WEBHOOKS} />
</div> </div>
<div class="mb-2.5">
<div class=" self-center text-xs font-medium mb-2">
{$i18n.t('Response Watermark')}
</div>
<Textarea
placeholder={$i18n.t('Enter a watermark for the response. Leave empty for none.')}
bind:value={adminConfig.RESPONSE_WATERMARK}
/>
</div>
<div class="mb-2.5 w-full justify-between"> <div class="mb-2.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('WebUI URL')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('WebUI URL')}</div>

View File

@ -157,6 +157,10 @@
const copyToClipboard = async (text) => { const copyToClipboard = async (text) => {
text = removeAllDetails(text); text = removeAllDetails(text);
if (($config?.ui?.response_watermark ?? '').trim() !== '') {
text = `${text}\n\n${$config?.ui?.response_watermark}`;
}
const res = await _copyToClipboard(text, $settings?.copyFormatted ?? false); const res = await _copyToClipboard(text, $settings?.copyFormatted ?? false);
if (res) { if (res) {
toast.success($i18n.t('Copying to clipboard was successful!')); toast.success($i18n.t('Copying to clipboard was successful!'));

View File

@ -7,7 +7,7 @@
export let minSize = null; export let minSize = null;
export let required = false; export let required = false;
export let className = export let className =
'w-full rounded-lg px-3 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden h-full'; 'w-full rounded-lg px-3.5 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden h-full';
let textareaElement; let textareaElement;

View File

@ -25,7 +25,7 @@
class="text-center dark:text-white text-2xl font-medium z-50" class="text-center dark:text-white text-2xl font-medium z-50"
style="white-space: pre-wrap;" style="white-space: pre-wrap;"
> >
{#if $config?.ui?.pending_user_overlay_title && $config?.ui?.pending_user_overlay_title.trim() !== ''} {#if ($config?.ui?.pending_user_overlay_title ?? '').trim() !== ''}
{$config.ui.pending_user_overlay_title} {$config.ui.pending_user_overlay_title}
{:else} {:else}
{$i18n.t('Account Activation Pending')}<br /> {$i18n.t('Account Activation Pending')}<br />
@ -37,7 +37,7 @@
class=" mt-4 text-center text-sm dark:text-gray-200 w-full" class=" mt-4 text-center text-sm dark:text-gray-200 w-full"
style="white-space: pre-wrap;" style="white-space: pre-wrap;"
> >
{#if $config?.ui?.pending_user_overlay_content && $config?.ui?.pending_user_overlay_content.trim() !== ''} {#if ($config?.ui?.pending_user_overlay_content ?? '').trim() !== ''}
{$config.ui.pending_user_overlay_content} {$config.ui.pending_user_overlay_content}
{:else} {:else}
{$i18n.t('Your account status is currently pending activation.')}{'\n'}{$i18n.t( {$i18n.t('Your account status is currently pending activation.')}{'\n'}{$i18n.t(