diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 7f3338c270..420508c38c 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1,20 +1,19 @@ +import base64 import json import logging import os import shutil import socket -import base64 from concurrent.futures import ThreadPoolExecutor - from datetime import datetime from pathlib import Path -from typing import Union, Optional +from typing import Optional, Union from urllib.parse import urlparse import redis import requests -from pydantic import BaseModel from authlib.integrations.starlette_client import OAuth +from pydantic import BaseModel from open_webui.env import ( DATA_DIR, @@ -97,11 +96,10 @@ class EndpointFilter(logging.Filter): return record.getMessage().find('/health') == -1 -# Filter out /health endpoint noise logging.getLogger('uvicorn.access').addFilter(EndpointFilter()) #################################### -# Config helpers +# Initialization #################################### @@ -141,547 +139,6 @@ CONFIG_DATA = _initialize_config( enable_oauth_persistent=ENABLE_OAUTH_PERSISTENT_CONFIG, ) - -ENABLE_API_KEYS = ConfigVar( - 'ENABLE_API_KEYS', - 'auth.enable_api_keys', - os.environ.get('ENABLE_API_KEYS', 'False').lower() == 'true', -) - -ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = ConfigVar( - 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS', - 'auth.api_key.endpoint_restrictions', - os.environ.get( - 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS', - os.environ.get('ENABLE_API_KEY_ENDPOINT_RESTRICTIONS', 'False'), - ).lower() - == 'true', -) - -API_KEYS_ALLOWED_ENDPOINTS = ConfigVar( - 'API_KEYS_ALLOWED_ENDPOINTS', - 'auth.api_key.allowed_endpoints', - os.environ.get('API_KEYS_ALLOWED_ENDPOINTS', os.environ.get('API_KEY_ALLOWED_ENDPOINTS', '')), -) - -JWT_EXPIRES_IN = ConfigVar('JWT_EXPIRES_IN', 'auth.jwt_expiry', os.environ.get('JWT_EXPIRES_IN', '4w')) - -if JWT_EXPIRES_IN.value == '-1': - log.warning( - "โš ๏ธ SECURITY WARNING: JWT_EXPIRES_IN is set to '-1'\n" - ' See: https://docs.openwebui.com/reference/env-configuration\n' - ) - -#################################### -# OAuth config -#################################### - -ENABLE_OAUTH_SIGNUP = ConfigVar( - 'ENABLE_OAUTH_SIGNUP', - 'oauth.enable_signup', - os.environ.get('ENABLE_OAUTH_SIGNUP', 'False').lower() == 'true', -) - -OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE = ConfigVar( - 'OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE', - 'oauth.refresh_token_include_scope', - os.environ.get('OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE', 'False').lower() == 'true', -) - - -OAUTH_MERGE_ACCOUNTS_BY_EMAIL = ConfigVar( - 'OAUTH_MERGE_ACCOUNTS_BY_EMAIL', - 'oauth.merge_accounts_by_email', - os.environ.get('OAUTH_MERGE_ACCOUNTS_BY_EMAIL', 'False').lower() == 'true', -) - -OAUTH_PROVIDERS = {} - -GOOGLE_CLIENT_ID = ConfigVar( - 'GOOGLE_CLIENT_ID', - 'oauth.google.client_id', - os.environ.get('GOOGLE_CLIENT_ID', ''), -) - -GOOGLE_CLIENT_SECRET = ConfigVar( - 'GOOGLE_CLIENT_SECRET', - 'oauth.google.client_secret', - os.environ.get('GOOGLE_CLIENT_SECRET', ''), -) - - -GOOGLE_OAUTH_SCOPE = ConfigVar( - 'GOOGLE_OAUTH_SCOPE', - 'oauth.google.scope', - os.environ.get('GOOGLE_OAUTH_SCOPE', 'openid email profile'), -) - -GOOGLE_REDIRECT_URI = ConfigVar( - 'GOOGLE_REDIRECT_URI', - 'oauth.google.redirect_uri', - os.environ.get('GOOGLE_REDIRECT_URI', ''), -) - -GOOGLE_OAUTH_AUTHORIZE_PARAMS = {} -_google_oauth_authorize_params = os.environ.get('GOOGLE_OAUTH_AUTHORIZE_PARAMS', '') -if _google_oauth_authorize_params: - try: - _parsed = json.loads(_google_oauth_authorize_params) - if isinstance(_parsed, dict): - GOOGLE_OAUTH_AUTHORIZE_PARAMS = _parsed - else: - log.warning('GOOGLE_OAUTH_AUTHORIZE_PARAMS must be a JSON object, ignoring') - except (json.JSONDecodeError, TypeError): - log.warning('GOOGLE_OAUTH_AUTHORIZE_PARAMS is not valid JSON, ignoring') - -MICROSOFT_CLIENT_ID = ConfigVar( - 'MICROSOFT_CLIENT_ID', - 'oauth.microsoft.client_id', - os.environ.get('MICROSOFT_CLIENT_ID', ''), -) - -MICROSOFT_CLIENT_SECRET = ConfigVar( - 'MICROSOFT_CLIENT_SECRET', - 'oauth.microsoft.client_secret', - os.environ.get('MICROSOFT_CLIENT_SECRET', ''), -) - -MICROSOFT_CLIENT_TENANT_ID = ConfigVar( - 'MICROSOFT_CLIENT_TENANT_ID', - 'oauth.microsoft.tenant_id', - os.environ.get('MICROSOFT_CLIENT_TENANT_ID', ''), -) - -MICROSOFT_CLIENT_LOGIN_BASE_URL = ConfigVar( - 'MICROSOFT_CLIENT_LOGIN_BASE_URL', - 'oauth.microsoft.login_base_url', - os.environ.get('MICROSOFT_CLIENT_LOGIN_BASE_URL', 'https://login.microsoftonline.com'), -) - -MICROSOFT_CLIENT_PICTURE_URL = ConfigVar( - 'MICROSOFT_CLIENT_PICTURE_URL', - 'oauth.microsoft.picture_url', - os.environ.get( - 'MICROSOFT_CLIENT_PICTURE_URL', - 'https://graph.microsoft.com/v1.0/me/photo/$value', - ), -) - - -MICROSOFT_OAUTH_SCOPE = ConfigVar( - 'MICROSOFT_OAUTH_SCOPE', - 'oauth.microsoft.scope', - os.environ.get('MICROSOFT_OAUTH_SCOPE', 'openid email profile'), -) - -MICROSOFT_REDIRECT_URI = ConfigVar( - 'MICROSOFT_REDIRECT_URI', - 'oauth.microsoft.redirect_uri', - os.environ.get('MICROSOFT_REDIRECT_URI', ''), -) - -GITHUB_CLIENT_ID = ConfigVar( - 'GITHUB_CLIENT_ID', - 'oauth.github.client_id', - os.environ.get('GITHUB_CLIENT_ID', ''), -) - -GITHUB_CLIENT_SECRET = ConfigVar( - 'GITHUB_CLIENT_SECRET', - 'oauth.github.client_secret', - os.environ.get('GITHUB_CLIENT_SECRET', ''), -) - -GITHUB_CLIENT_SCOPE = ConfigVar( - 'GITHUB_CLIENT_SCOPE', - 'oauth.github.scope', - os.environ.get('GITHUB_CLIENT_SCOPE', 'user:email'), -) - -GITHUB_CLIENT_REDIRECT_URI = ConfigVar( - 'GITHUB_CLIENT_REDIRECT_URI', - 'oauth.github.redirect_uri', - os.environ.get('GITHUB_CLIENT_REDIRECT_URI', ''), -) - -OAUTH_CLIENT_ID = ConfigVar( - 'OAUTH_CLIENT_ID', - 'oauth.oidc.client_id', - os.environ.get('OAUTH_CLIENT_ID', ''), -) - -OAUTH_CLIENT_SECRET = ConfigVar( - 'OAUTH_CLIENT_SECRET', - 'oauth.oidc.client_secret', - os.environ.get('OAUTH_CLIENT_SECRET', ''), -) - -OPENID_PROVIDER_URL = ConfigVar( - 'OPENID_PROVIDER_URL', - 'oauth.oidc.provider_url', - os.environ.get('OPENID_PROVIDER_URL', ''), -) - -OPENID_END_SESSION_ENDPOINT = ConfigVar( - 'OPENID_END_SESSION_ENDPOINT', - 'oauth.oidc.end_session_endpoint', - os.environ.get('OPENID_END_SESSION_ENDPOINT', ''), -) - -OPENID_REDIRECT_URI = ConfigVar( - 'OPENID_REDIRECT_URI', - 'oauth.oidc.redirect_uri', - os.environ.get('OPENID_REDIRECT_URI', ''), -) - -OAUTH_SCOPES = ConfigVar( - 'OAUTH_SCOPES', - 'oauth.oidc.scopes', - os.environ.get('OAUTH_SCOPES', 'openid email profile'), -) - -OAUTH_TIMEOUT = ConfigVar( - 'OAUTH_TIMEOUT', - 'oauth.oidc.oauth_timeout', - os.environ.get('OAUTH_TIMEOUT', ''), -) - -OAUTH_TOKEN_ENDPOINT_AUTH_METHOD = ConfigVar( - 'OAUTH_TOKEN_ENDPOINT_AUTH_METHOD', - 'oauth.oidc.token_endpoint_auth_method', - os.environ.get('OAUTH_TOKEN_ENDPOINT_AUTH_METHOD', None), -) - -OAUTH_CODE_CHALLENGE_METHOD = ConfigVar( - 'OAUTH_CODE_CHALLENGE_METHOD', - 'oauth.oidc.code_challenge_method', - os.environ.get('OAUTH_CODE_CHALLENGE_METHOD', None), -) - -OAUTH_PROVIDER_NAME = ConfigVar( - 'OAUTH_PROVIDER_NAME', - 'oauth.oidc.provider_name', - os.environ.get('OAUTH_PROVIDER_NAME', 'SSO'), -) - -OAUTH_SUB_CLAIM = ConfigVar( - 'OAUTH_SUB_CLAIM', - 'oauth.oidc.sub_claim', - os.environ.get('OAUTH_SUB_CLAIM', None), -) - -OAUTH_USERNAME_CLAIM = ConfigVar( - 'OAUTH_USERNAME_CLAIM', - 'oauth.oidc.username_claim', - os.environ.get('OAUTH_USERNAME_CLAIM', 'name'), -) - - -OAUTH_PICTURE_CLAIM = ConfigVar( - 'OAUTH_PICTURE_CLAIM', - 'oauth.oidc.avatar_claim', - os.environ.get('OAUTH_PICTURE_CLAIM', 'picture'), -) - -OAUTH_EMAIL_CLAIM = ConfigVar( - 'OAUTH_EMAIL_CLAIM', - 'oauth.oidc.email_claim', - os.environ.get('OAUTH_EMAIL_CLAIM', 'email'), -) - -OAUTH_GROUPS_CLAIM = ConfigVar( - 'OAUTH_GROUPS_CLAIM', - 'oauth.oidc.group_claim', - os.environ.get('OAUTH_GROUPS_CLAIM', os.environ.get('OAUTH_GROUP_CLAIM', 'groups')), -) - -FEISHU_CLIENT_ID = ConfigVar( - 'FEISHU_CLIENT_ID', - 'oauth.feishu.client_id', - os.environ.get('FEISHU_CLIENT_ID', ''), -) - -FEISHU_CLIENT_SECRET = ConfigVar( - 'FEISHU_CLIENT_SECRET', - 'oauth.feishu.client_secret', - os.environ.get('FEISHU_CLIENT_SECRET', ''), -) - -FEISHU_OAUTH_SCOPE = ConfigVar( - 'FEISHU_OAUTH_SCOPE', - 'oauth.feishu.scope', - os.environ.get('FEISHU_OAUTH_SCOPE', 'contact:user.base:readonly'), -) - -FEISHU_REDIRECT_URI = ConfigVar( - 'FEISHU_REDIRECT_URI', - 'oauth.feishu.redirect_uri', - os.environ.get('FEISHU_REDIRECT_URI', ''), -) - -ENABLE_OAUTH_ROLE_MANAGEMENT = ConfigVar( - 'ENABLE_OAUTH_ROLE_MANAGEMENT', - 'oauth.enable_role_mapping', - os.environ.get('ENABLE_OAUTH_ROLE_MANAGEMENT', 'False').lower() == 'true', -) - -ENABLE_OAUTH_GROUP_MANAGEMENT = ConfigVar( - 'ENABLE_OAUTH_GROUP_MANAGEMENT', - 'oauth.enable_group_mapping', - os.environ.get('ENABLE_OAUTH_GROUP_MANAGEMENT', 'False').lower() == 'true', -) - -ENABLE_OAUTH_GROUP_CREATION = ConfigVar( - 'ENABLE_OAUTH_GROUP_CREATION', - 'oauth.enable_group_creation', - os.environ.get('ENABLE_OAUTH_GROUP_CREATION', 'False').lower() == 'true', -) - - -oauth_group_default_share = os.environ.get('OAUTH_GROUP_DEFAULT_SHARE', 'true').strip().lower() -OAUTH_GROUP_DEFAULT_SHARE = ConfigVar( - 'OAUTH_GROUP_DEFAULT_SHARE', - 'oauth.group_default_share', - ('members' if oauth_group_default_share == 'members' else oauth_group_default_share == 'true'), -) - - -OAUTH_BLOCKED_GROUPS = ConfigVar( - 'OAUTH_BLOCKED_GROUPS', - 'oauth.blocked_groups', - os.environ.get('OAUTH_BLOCKED_GROUPS', '[]'), -) - -OAUTH_GROUPS_SEPARATOR = os.environ.get('OAUTH_GROUPS_SEPARATOR', ';') - -OAUTH_ROLES_CLAIM = ConfigVar( - 'OAUTH_ROLES_CLAIM', - 'oauth.roles_claim', - os.environ.get('OAUTH_ROLES_CLAIM', 'roles'), -) - -OAUTH_ROLES_SEPARATOR = os.environ.get('OAUTH_ROLES_SEPARATOR', ',') - -OAUTH_ALLOWED_ROLES = ConfigVar( - 'OAUTH_ALLOWED_ROLES', - 'oauth.allowed_roles', - [ - role.strip() - for role in os.environ.get('OAUTH_ALLOWED_ROLES', f'user{OAUTH_ROLES_SEPARATOR}admin').split( - OAUTH_ROLES_SEPARATOR - ) - if role - ], -) - -OAUTH_ADMIN_ROLES = ConfigVar( - 'OAUTH_ADMIN_ROLES', - 'oauth.admin_roles', - [role.strip() for role in os.environ.get('OAUTH_ADMIN_ROLES', 'admin').split(OAUTH_ROLES_SEPARATOR) if role], -) - -OAUTH_ALLOWED_DOMAINS = ConfigVar( - 'OAUTH_ALLOWED_DOMAINS', - 'oauth.allowed_domains', - [domain.strip() for domain in os.environ.get('OAUTH_ALLOWED_DOMAINS', '*').split(',')], -) - -OAUTH_UPDATE_PICTURE_ON_LOGIN = ConfigVar( - 'OAUTH_UPDATE_PICTURE_ON_LOGIN', - 'oauth.update_picture_on_login', - os.environ.get('OAUTH_UPDATE_PICTURE_ON_LOGIN', 'False').lower() == 'true', -) - -OAUTH_UPDATE_NAME_ON_LOGIN = ConfigVar( - 'OAUTH_UPDATE_NAME_ON_LOGIN', - 'oauth.update_name_on_login', - os.environ.get('OAUTH_UPDATE_NAME_ON_LOGIN', 'False').lower() == 'true', -) - -OAUTH_UPDATE_EMAIL_ON_LOGIN = ConfigVar( - 'OAUTH_UPDATE_EMAIL_ON_LOGIN', - 'oauth.update_email_on_login', - os.environ.get('OAUTH_UPDATE_EMAIL_ON_LOGIN', 'False').lower() == 'true', -) - -OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID = ( - os.environ.get('OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID', 'False').lower() == 'true' -) - -OAUTH_AUDIENCE = ConfigVar( - 'OAUTH_AUDIENCE', - 'oauth.audience', - os.environ.get('OAUTH_AUDIENCE', ''), -) - -OAUTH_AUTHORIZE_PARAMS = {} -_oauth_authorize_params = os.environ.get('OAUTH_AUTHORIZE_PARAMS', '') -if _oauth_authorize_params: - try: - _parsed = json.loads(_oauth_authorize_params) - if isinstance(_parsed, dict): - OAUTH_AUTHORIZE_PARAMS = _parsed - else: - log.warning('OAUTH_AUTHORIZE_PARAMS must be a JSON object, ignoring') - except (json.JSONDecodeError, TypeError): - log.warning('OAUTH_AUTHORIZE_PARAMS is not valid JSON, ignoring') - - -def load_oauth_providers(): - OAUTH_PROVIDERS.clear() - if GOOGLE_CLIENT_ID.value and GOOGLE_CLIENT_SECRET.value: - - def google_oauth_register(oauth: OAuth): - client = oauth.register( - name='google', - client_id=GOOGLE_CLIENT_ID.value, - client_secret=GOOGLE_CLIENT_SECRET.value, - server_metadata_url='https://accounts.google.com/.well-known/openid-configuration', - client_kwargs={ - 'scope': GOOGLE_OAUTH_SCOPE.value, - **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), - }, - redirect_uri=GOOGLE_REDIRECT_URI.value, - **({'authorize_params': GOOGLE_OAUTH_AUTHORIZE_PARAMS} if GOOGLE_OAUTH_AUTHORIZE_PARAMS else {}), - ) - return client - - OAUTH_PROVIDERS['google'] = { - 'register': google_oauth_register, - } - - if MICROSOFT_CLIENT_ID.value and MICROSOFT_CLIENT_SECRET.value and MICROSOFT_CLIENT_TENANT_ID.value: - - def microsoft_oauth_register(oauth: OAuth): - client = oauth.register( - name='microsoft', - client_id=MICROSOFT_CLIENT_ID.value, - client_secret=MICROSOFT_CLIENT_SECRET.value, - server_metadata_url=f'{MICROSOFT_CLIENT_LOGIN_BASE_URL.value}/{MICROSOFT_CLIENT_TENANT_ID.value}/v2.0/.well-known/openid-configuration?appid={MICROSOFT_CLIENT_ID.value}', - client_kwargs={ - 'scope': MICROSOFT_OAUTH_SCOPE.value, - **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), - }, - redirect_uri=MICROSOFT_REDIRECT_URI.value, - ) - return client - - OAUTH_PROVIDERS['microsoft'] = { - 'picture_url': MICROSOFT_CLIENT_PICTURE_URL.value, - 'register': microsoft_oauth_register, - } - - if GITHUB_CLIENT_ID.value and GITHUB_CLIENT_SECRET.value: - - def github_oauth_register(oauth: OAuth): - client = oauth.register( - name='github', - client_id=GITHUB_CLIENT_ID.value, - client_secret=GITHUB_CLIENT_SECRET.value, - access_token_url='https://github.com/login/oauth/access_token', - authorize_url='https://github.com/login/oauth/authorize', - api_base_url='https://api.github.com', - userinfo_endpoint='https://api.github.com/user', - client_kwargs={ - 'scope': GITHUB_CLIENT_SCOPE.value, - **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), - }, - redirect_uri=GITHUB_CLIENT_REDIRECT_URI.value, - ) - return client - - OAUTH_PROVIDERS['github'] = { - 'register': github_oauth_register, - 'sub_claim': 'id', - } - - if ( - OAUTH_CLIENT_ID.value - and (OAUTH_CLIENT_SECRET.value or OAUTH_CODE_CHALLENGE_METHOD.value) - and OPENID_PROVIDER_URL.value - ): - - def oidc_oauth_register(oauth: OAuth): - client_kwargs = { - 'scope': OAUTH_SCOPES.value, - **( - {'token_endpoint_auth_method': OAUTH_TOKEN_ENDPOINT_AUTH_METHOD.value} - if OAUTH_TOKEN_ENDPOINT_AUTH_METHOD.value - else {} - ), - **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), - } - - if OAUTH_CODE_CHALLENGE_METHOD.value and OAUTH_CODE_CHALLENGE_METHOD.value == 'S256': - client_kwargs['code_challenge_method'] = 'S256' - elif OAUTH_CODE_CHALLENGE_METHOD.value: - raise Exception( - 'Code challenge methods other than "%s" not supported. Given: "%s"' - % ('S256', OAUTH_CODE_CHALLENGE_METHOD.value) - ) - - client = oauth.register( - name='oidc', - client_id=OAUTH_CLIENT_ID.value, - client_secret=OAUTH_CLIENT_SECRET.value, - server_metadata_url=OPENID_PROVIDER_URL.value, - client_kwargs=client_kwargs, - redirect_uri=OPENID_REDIRECT_URI.value, - ) - return client - - OAUTH_PROVIDERS['oidc'] = { - 'name': OAUTH_PROVIDER_NAME.value, - 'register': oidc_oauth_register, - } - - if FEISHU_CLIENT_ID.value and FEISHU_CLIENT_SECRET.value: - - def feishu_oauth_register(oauth: OAuth): - client = oauth.register( - name='feishu', - client_id=FEISHU_CLIENT_ID.value, - client_secret=FEISHU_CLIENT_SECRET.value, - access_token_url='https://open.feishu.cn/open-apis/authen/v2/oauth/token', - authorize_url='https://accounts.feishu.cn/open-apis/authen/v1/authorize', - api_base_url='https://open.feishu.cn/open-apis', - userinfo_endpoint='https://open.feishu.cn/open-apis/authen/v1/user_info', - client_kwargs={ - 'scope': FEISHU_OAUTH_SCOPE.value, - **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), - }, - redirect_uri=FEISHU_REDIRECT_URI.value, - ) - return client - - OAUTH_PROVIDERS['feishu'] = { - 'register': feishu_oauth_register, - 'sub_claim': 'user_id', - } - - configured_providers = [] - if GOOGLE_CLIENT_ID.value: - configured_providers.append('Google') - if MICROSOFT_CLIENT_ID.value: - configured_providers.append('Microsoft') - if GITHUB_CLIENT_ID.value: - configured_providers.append('GitHub') - if FEISHU_CLIENT_ID.value: - configured_providers.append('Feishu') - - if configured_providers and not OPENID_PROVIDER_URL.value and not OPENID_END_SESSION_ENDPOINT.value: - provider_list = ', '.join(configured_providers) - log.warning( - f'โš ๏ธ OAuth providers configured ({provider_list}) but OPENID_PROVIDER_URL not set - logout will not work!' - ) - log.warning( - f"Set OPENID_PROVIDER_URL to your OAuth provider's OpenID Connect discovery endpoint," - f' or set OPENID_END_SESSION_ENDPOINT to a custom logout URL to fix logout functionality.' - ) - - -load_oauth_providers() - #################################### # Static DIR #################################### @@ -733,43 +190,6 @@ if frontend_loader.exists(): logging.error(f'An error occurred: {e}') -#################################### -# CUSTOM_NAME (Legacy) -#################################### - -CUSTOM_NAME = os.environ.get('CUSTOM_NAME', '') - -if CUSTOM_NAME: - try: - r = requests.get(f'https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}') - data = r.json() - if r.ok: - if 'logo' in data: - WEBUI_FAVICON_URL = url = ( - f'https://api.openwebui.com{data["logo"]}' if data['logo'][0] == '/' else data['logo'] - ) - - r = requests.get(url, stream=True) - if r.status_code == 200: - with open(f'{STATIC_DIR}/favicon.png', 'wb') as f: - r.raw.decode_content = True - shutil.copyfileobj(r.raw, f) - - if 'splash' in data: - url = f'https://api.openwebui.com{data["splash"]}' if data['splash'][0] == '/' else data['splash'] - - r = requests.get(url, stream=True) - if r.status_code == 200: - with open(f'{STATIC_DIR}/splash.png', 'wb') as f: - r.raw.decode_content = True - shutil.copyfileobj(r.raw, f) - - WEBUI_NAME = data['name'] - except Exception as e: - log.exception(e) - pass - - #################################### # STORAGE PROVIDER #################################### @@ -810,6 +230,43 @@ CACHE_DIR = DATA_DIR / 'cache' CACHE_DIR.mkdir(parents=True, exist_ok=True) +#################################### +# CUSTOM_NAME (Legacy) +#################################### + +CUSTOM_NAME = os.environ.get('CUSTOM_NAME', '') + +if CUSTOM_NAME: + try: + r = requests.get(f'https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}') + data = r.json() + if r.ok: + if 'logo' in data: + WEBUI_FAVICON_URL = url = ( + f'https://api.openwebui.com{data["logo"]}' if data['logo'][0] == '/' else data['logo'] + ) + + r = requests.get(url, stream=True) + if r.status_code == 200: + with open(f'{STATIC_DIR}/favicon.png', 'wb') as f: + r.raw.decode_content = True + shutil.copyfileobj(r.raw, f) + + if 'splash' in data: + url = f'https://api.openwebui.com{data["splash"]}' if data['splash'][0] == '/' else data['splash'] + + r = requests.get(url, stream=True) + if r.status_code == 200: + with open(f'{STATIC_DIR}/splash.png', 'wb') as f: + r.raw.decode_content = True + shutil.copyfileobj(r.raw, f) + + WEBUI_NAME = data['name'] + except Exception as e: + log.exception(e) + pass + + #################################### # DIRECT CONNECTIONS #################################### @@ -1004,938 +461,6 @@ try: except Exception: TERMINAL_PROXY_HEADERS = {} -#################################### -# WEBUI -#################################### - - -WEBUI_URL = ConfigVar('WEBUI_URL', 'webui.url', os.environ.get('WEBUI_URL', '')) - - -ENABLE_SIGNUP = ConfigVar( - 'ENABLE_SIGNUP', - 'ui.enable_signup', - (False if not WEBUI_AUTH else os.environ.get('ENABLE_SIGNUP', 'True').lower() == 'true'), -) - -ENABLE_LOGIN_FORM = ConfigVar( - 'ENABLE_LOGIN_FORM', - 'ui.enable_login_form', - os.environ.get('ENABLE_LOGIN_FORM', 'True').lower() == 'true', -) - -ENABLE_PASSWORD_CHANGE_FORM = ConfigVar( - 'ENABLE_PASSWORD_CHANGE_FORM', - 'ui.enable_password_change_form', - os.environ.get('ENABLE_PASSWORD_CHANGE_FORM', 'True').lower() == 'true', -) - -ENABLE_PASSWORD_AUTH = os.environ.get('ENABLE_PASSWORD_AUTH', 'True').lower() == 'true' - -DEFAULT_LOCALE = ConfigVar( - 'DEFAULT_LOCALE', - 'ui.default_locale', - os.environ.get('DEFAULT_LOCALE', ''), -) - -DEFAULT_MODELS = ConfigVar('DEFAULT_MODELS', 'ui.default_models', os.environ.get('DEFAULT_MODELS', None)) - -DEFAULT_PINNED_MODELS = ConfigVar( - 'DEFAULT_PINNED_MODELS', - 'ui.default_pinned_models', - os.environ.get('DEFAULT_PINNED_MODELS', None), -) - -try: - default_prompt_suggestions = json.loads(os.environ.get('DEFAULT_PROMPT_SUGGESTIONS', '[]')) -except Exception as e: - log.exception(f'Error loading DEFAULT_PROMPT_SUGGESTIONS: {e}') - default_prompt_suggestions = [] -if default_prompt_suggestions == []: - default_prompt_suggestions = [ - { - 'title': ['Help me study', 'vocabulary for a college entrance exam'], - 'content': "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.", - }, - { - 'title': ['Give me ideas', "for what to do with my kids' art"], - 'content': "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.", - }, - { - 'title': ['Tell me a fun fact', 'about the Roman Empire'], - 'content': 'Tell me a random fun fact about the Roman Empire', - }, - { - 'title': ['Show me a code snippet', "of a website's sticky header"], - 'content': "Show me a code snippet of a website's sticky header in CSS and JavaScript.", - }, - { - 'title': [ - 'Explain options trading', - "if I'm familiar with buying and selling stocks", - ], - 'content': "Explain options trading in simple terms if I'm familiar with buying and selling stocks.", - }, - { - 'title': ['Overcome procrastination', 'give me tips'], - 'content': 'Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?', - }, - ] - -DEFAULT_PROMPT_SUGGESTIONS = ConfigVar( - 'DEFAULT_PROMPT_SUGGESTIONS', - 'ui.prompt_suggestions', - default_prompt_suggestions, -) - -MODEL_ORDER_LIST = ConfigVar( - 'MODEL_ORDER_LIST', - 'ui.model_order_list', - [], -) - -try: - default_model_metadata = json.loads(os.environ.get('DEFAULT_MODEL_METADATA', '{}')) -except Exception as e: - log.exception(f'Error loading DEFAULT_MODEL_METADATA: {e}') - default_model_metadata = {} - -DEFAULT_MODEL_METADATA = ConfigVar( - 'DEFAULT_MODEL_METADATA', - 'models.default_metadata', - default_model_metadata, -) - -try: - default_model_params = json.loads(os.environ.get('DEFAULT_MODEL_PARAMS', '{}')) -except Exception as e: - log.exception(f'Error loading DEFAULT_MODEL_PARAMS: {e}') - default_model_params = {} - -DEFAULT_MODEL_PARAMS = ConfigVar( - 'DEFAULT_MODEL_PARAMS', - 'models.default_params', - default_model_params, -) - -DEFAULT_USER_ROLE = ConfigVar( - 'DEFAULT_USER_ROLE', - 'ui.default_user_role', - os.getenv('DEFAULT_USER_ROLE', 'pending'), -) - -DEFAULT_GROUP_ID = ConfigVar( - 'DEFAULT_GROUP_ID', - 'ui.default_group_id', - os.environ.get('DEFAULT_GROUP_ID', ''), -) - -PENDING_USER_OVERLAY_TITLE = ConfigVar( - 'PENDING_USER_OVERLAY_TITLE', - 'ui.pending_user_overlay_title', - os.environ.get('PENDING_USER_OVERLAY_TITLE', ''), -) - -PENDING_USER_OVERLAY_CONTENT = ConfigVar( - 'PENDING_USER_OVERLAY_CONTENT', - 'ui.pending_user_overlay_content', - os.environ.get('PENDING_USER_OVERLAY_CONTENT', ''), -) - - -RESPONSE_WATERMARK = ConfigVar( - 'RESPONSE_WATERMARK', - 'ui.watermark', - os.environ.get('RESPONSE_WATERMARK', ''), -) - -IFRAME_CSP = os.environ.get('IFRAME_CSP', '') - -USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT', 'False').lower() == 'true' -) - - -USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' -) - - -USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING = ( - os.environ.get('USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' -) - - -USER_PERMISSIONS_NOTES_ALLOW_SHARING = os.environ.get('USER_PERMISSIONS_NOTES_ALLOW_SHARING', 'False').lower() == 'true' - -USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING = ( - os.environ.get('USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING = ( - os.environ.get('USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS = ( - os.environ.get('USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS', 'True').lower() == 'true' -) - - -USER_PERMISSIONS_CHAT_CONTROLS = os.environ.get('USER_PERMISSIONS_CHAT_CONTROLS', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_VALVES = os.environ.get('USER_PERMISSIONS_CHAT_VALVES', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_SYSTEM_PROMPT = os.environ.get('USER_PERMISSIONS_CHAT_SYSTEM_PROMPT', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_PARAMS = os.environ.get('USER_PERMISSIONS_CHAT_PARAMS', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_FILE_UPLOAD = os.environ.get('USER_PERMISSIONS_CHAT_FILE_UPLOAD', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_WEB_UPLOAD = os.environ.get('USER_PERMISSIONS_CHAT_WEB_UPLOAD', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_DELETE = os.environ.get('USER_PERMISSIONS_CHAT_DELETE', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_DELETE_MESSAGE = os.environ.get('USER_PERMISSIONS_CHAT_DELETE_MESSAGE', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE = ( - os.environ.get('USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE', 'True').lower() == 'true' -) - -USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE = ( - os.environ.get('USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE', 'True').lower() == 'true' -) - -USER_PERMISSIONS_CHAT_RATE_RESPONSE = os.environ.get('USER_PERMISSIONS_CHAT_RATE_RESPONSE', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_EDIT = os.environ.get('USER_PERMISSIONS_CHAT_EDIT', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_SHARE = os.environ.get('USER_PERMISSIONS_CHAT_SHARE', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING = ( - os.environ.get('USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' -) - -USER_PERMISSIONS_CHAT_EXPORT = os.environ.get('USER_PERMISSIONS_CHAT_EXPORT', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_STT = os.environ.get('USER_PERMISSIONS_CHAT_STT', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_TTS = os.environ.get('USER_PERMISSIONS_CHAT_TTS', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_CALL = os.environ.get('USER_PERMISSIONS_CHAT_CALL', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_MULTIPLE_MODELS = ( - os.environ.get('USER_PERMISSIONS_CHAT_MULTIPLE_MODELS', 'True').lower() == 'true' -) - -USER_PERMISSIONS_CHAT_TEMPORARY = os.environ.get('USER_PERMISSIONS_CHAT_TEMPORARY', 'True').lower() == 'true' - -USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED = ( - os.environ.get('USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED', 'False').lower() == 'true' -) - - -USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS = ( - os.environ.get('USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS', 'False').lower() == 'true' -) - -USER_PERMISSIONS_FEATURES_WEB_SEARCH = os.environ.get('USER_PERMISSIONS_FEATURES_WEB_SEARCH', 'True').lower() == 'true' - -USER_PERMISSIONS_FEATURES_IMAGE_GENERATION = ( - os.environ.get('USER_PERMISSIONS_FEATURES_IMAGE_GENERATION', 'True').lower() == 'true' -) - -USER_PERMISSIONS_FEATURES_CODE_INTERPRETER = ( - os.environ.get('USER_PERMISSIONS_FEATURES_CODE_INTERPRETER', 'True').lower() == 'true' -) - -USER_PERMISSIONS_FEATURES_FOLDERS = os.environ.get('USER_PERMISSIONS_FEATURES_FOLDERS', 'True').lower() == 'true' - -USER_PERMISSIONS_FEATURES_NOTES = os.environ.get('USER_PERMISSIONS_FEATURES_NOTES', 'True').lower() == 'true' - -USER_PERMISSIONS_FEATURES_CHANNELS = os.environ.get('USER_PERMISSIONS_FEATURES_CHANNELS', 'True').lower() == 'true' - -USER_PERMISSIONS_FEATURES_API_KEYS = os.environ.get('USER_PERMISSIONS_FEATURES_API_KEYS', 'False').lower() == 'true' - -USER_PERMISSIONS_FEATURES_MEMORIES = os.environ.get('USER_PERMISSIONS_FEATURES_MEMORIES', 'True').lower() == 'true' - -USER_PERMISSIONS_FEATURES_AUTOMATIONS = ( - os.environ.get('USER_PERMISSIONS_FEATURES_AUTOMATIONS', 'False').lower() == 'true' -) - -USER_PERMISSIONS_FEATURES_CALENDAR = os.environ.get('USER_PERMISSIONS_FEATURES_CALENDAR', 'True').lower() == 'true' - - -USER_PERMISSIONS_SETTINGS_INTERFACE = os.environ.get('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true' - - -DEFAULT_USER_PERMISSIONS = { - 'workspace': { - 'models': USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS, - 'knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS, - 'prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS, - 'tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS, - 'skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS, - 'models_import': USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT, - 'models_export': USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT, - 'prompts_import': USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT, - 'prompts_export': USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT, - 'tools_import': USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT, - 'tools_export': USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT, - }, - 'sharing': { - 'models': USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING, - 'public_models': USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING, - 'knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING, - 'public_knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING, - 'prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING, - 'public_prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING, - 'tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING, - 'public_tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING, - 'skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING, - 'public_skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING, - 'notes': USER_PERMISSIONS_NOTES_ALLOW_SHARING, - 'public_notes': USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING, - 'public_chats': USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING, - 'public_calendars': USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING, - }, - 'access_grants': { - 'allow_users': USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS, - }, - 'chat': { - 'controls': USER_PERMISSIONS_CHAT_CONTROLS, - 'valves': USER_PERMISSIONS_CHAT_VALVES, - 'system_prompt': USER_PERMISSIONS_CHAT_SYSTEM_PROMPT, - 'params': USER_PERMISSIONS_CHAT_PARAMS, - 'file_upload': USER_PERMISSIONS_CHAT_FILE_UPLOAD, - 'web_upload': USER_PERMISSIONS_CHAT_WEB_UPLOAD, - 'delete': USER_PERMISSIONS_CHAT_DELETE, - 'delete_message': USER_PERMISSIONS_CHAT_DELETE_MESSAGE, - 'continue_response': USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE, - 'regenerate_response': USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE, - 'rate_response': USER_PERMISSIONS_CHAT_RATE_RESPONSE, - 'edit': USER_PERMISSIONS_CHAT_EDIT, - 'share': USER_PERMISSIONS_CHAT_SHARE, - 'export': USER_PERMISSIONS_CHAT_EXPORT, - 'stt': USER_PERMISSIONS_CHAT_STT, - 'tts': USER_PERMISSIONS_CHAT_TTS, - 'call': USER_PERMISSIONS_CHAT_CALL, - 'multiple_models': USER_PERMISSIONS_CHAT_MULTIPLE_MODELS, - 'temporary': USER_PERMISSIONS_CHAT_TEMPORARY, - 'temporary_enforced': USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED, - }, - 'features': { - # General features - 'api_keys': USER_PERMISSIONS_FEATURES_API_KEYS, - 'notes': USER_PERMISSIONS_FEATURES_NOTES, - 'folders': USER_PERMISSIONS_FEATURES_FOLDERS, - 'channels': USER_PERMISSIONS_FEATURES_CHANNELS, - 'direct_tool_servers': USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS, - # Chat features - 'web_search': USER_PERMISSIONS_FEATURES_WEB_SEARCH, - 'image_generation': USER_PERMISSIONS_FEATURES_IMAGE_GENERATION, - 'code_interpreter': USER_PERMISSIONS_FEATURES_CODE_INTERPRETER, - 'memories': USER_PERMISSIONS_FEATURES_MEMORIES, - 'automations': USER_PERMISSIONS_FEATURES_AUTOMATIONS, - 'calendar': USER_PERMISSIONS_FEATURES_CALENDAR, - }, - 'settings': { - 'interface': USER_PERMISSIONS_SETTINGS_INTERFACE, - }, -} - -USER_PERMISSIONS = ConfigVar( - 'USER_PERMISSIONS', - 'user.permissions', - DEFAULT_USER_PERMISSIONS, -) - -ENABLE_FOLDERS = ConfigVar( - 'ENABLE_FOLDERS', - 'folders.enable', - os.environ.get('ENABLE_FOLDERS', 'True').lower() == 'true', -) - -FOLDER_MAX_FILE_COUNT = ConfigVar( - 'FOLDER_MAX_FILE_COUNT', - 'folders.max_file_count', - os.environ.get('FOLDER_MAX_FILE_COUNT', ''), -) - -ENABLE_CHANNELS = ConfigVar( - 'ENABLE_CHANNELS', - 'channels.enable', - os.environ.get('ENABLE_CHANNELS', 'False').lower() == 'true', -) - -ENABLE_CALENDAR = ConfigVar( - 'ENABLE_CALENDAR', - 'calendar.enable', - os.environ.get('ENABLE_CALENDAR', 'True').lower() == 'true', -) - -ENABLE_AUTOMATIONS = ConfigVar( - 'ENABLE_AUTOMATIONS', - 'automations.enable', - os.environ.get('ENABLE_AUTOMATIONS', 'True').lower() == 'true', -) - -AUTOMATION_MAX_COUNT = ConfigVar( - 'AUTOMATION_MAX_COUNT', - 'automations.max_count', - os.environ.get('AUTOMATION_MAX_COUNT', ''), -) - -AUTOMATION_MIN_INTERVAL = ConfigVar( - 'AUTOMATION_MIN_INTERVAL', - 'automations.min_interval', - os.environ.get('AUTOMATION_MIN_INTERVAL', ''), -) - -ENABLE_NOTES = ConfigVar( - 'ENABLE_NOTES', - 'notes.enable', - os.environ.get('ENABLE_NOTES', 'True').lower() == 'true', -) - -ENABLE_USER_STATUS = ConfigVar( - 'ENABLE_USER_STATUS', - 'users.enable_status', - os.environ.get('ENABLE_USER_STATUS', 'True').lower() == 'true', -) - -ENABLE_EVALUATION_ARENA_MODELS = ConfigVar( - 'ENABLE_EVALUATION_ARENA_MODELS', - 'evaluation.arena.enable', - os.environ.get('ENABLE_EVALUATION_ARENA_MODELS', 'True').lower() == 'true', -) -EVALUATION_ARENA_MODELS = ConfigVar( - 'EVALUATION_ARENA_MODELS', - 'evaluation.arena.models', - [], -) - -DEFAULT_ARENA_MODEL = { - 'id': 'arena-model', - 'name': 'Arena Model', - 'meta': { - 'profile_image_url': '/favicon.png', - 'description': 'Submit your questions to anonymous AI chatbots and vote on the best response.', - 'model_ids': None, - }, -} - -WEBHOOK_URL = ConfigVar('WEBHOOK_URL', 'webhook_url', os.environ.get('WEBHOOK_URL', '')) - -ENABLE_ADMIN_EXPORT = os.environ.get('ENABLE_ADMIN_EXPORT', 'True').lower() == 'true' - -ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS = ( - os.environ.get('ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS', 'True').lower() == 'true' -) - -BYPASS_ADMIN_ACCESS_CONTROL = ( - os.environ.get( - 'BYPASS_ADMIN_ACCESS_CONTROL', - os.environ.get('ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS', 'True'), - ).lower() - == 'true' -) - -ENABLE_ADMIN_CHAT_ACCESS = os.environ.get('ENABLE_ADMIN_CHAT_ACCESS', 'True').lower() == 'true' - -ENABLE_ADMIN_ANALYTICS = os.environ.get('ENABLE_ADMIN_ANALYTICS', 'True').lower() == 'true' - -ENABLE_COMMUNITY_SHARING = ConfigVar( - 'ENABLE_COMMUNITY_SHARING', - 'ui.enable_community_sharing', - os.environ.get('ENABLE_COMMUNITY_SHARING', 'True').lower() == 'true', -) - -ENABLE_MESSAGE_RATING = ConfigVar( - 'ENABLE_MESSAGE_RATING', - 'ui.enable_message_rating', - os.environ.get('ENABLE_MESSAGE_RATING', 'True').lower() == 'true', -) - -ENABLE_USER_WEBHOOKS = ConfigVar( - 'ENABLE_USER_WEBHOOKS', - 'ui.enable_user_webhooks', - os.environ.get('ENABLE_USER_WEBHOOKS', 'False').lower() == 'true', -) - -# FastAPI / AnyIO settings -THREAD_POOL_SIZE = os.getenv('THREAD_POOL_SIZE', None) - -if THREAD_POOL_SIZE is not None and isinstance(THREAD_POOL_SIZE, str): - try: - THREAD_POOL_SIZE = int(THREAD_POOL_SIZE) - except ValueError: - log.warning(f'THREAD_POOL_SIZE is not a valid integer: {THREAD_POOL_SIZE}. Defaulting to None.') - THREAD_POOL_SIZE = None - - -def validate_cors_origin(origin): - parsed_url = urlparse(origin) - - # Check if the scheme is either http or https, or a custom scheme - schemes = ['http', 'https'] + CORS_ALLOW_CUSTOM_SCHEME - if parsed_url.scheme not in schemes: - raise ValueError( - f"Invalid scheme in CORS_ALLOW_ORIGIN: '{origin}'. Only 'http' and 'https' and CORS_ALLOW_CUSTOM_SCHEME are allowed." - ) - - # Ensure that the netloc (domain + port) is present, indicating it's a valid URL - if not parsed_url.netloc: - raise ValueError(f"Invalid URL structure in CORS_ALLOW_ORIGIN: '{origin}'.") - - -# For production, you should only need one host as -# fastapi serves the svelte-kit built frontend and backend from the same host and port. -# To test CORS_ALLOW_ORIGIN locally, you can set something like -# CORS_ALLOW_ORIGIN=http://localhost:5173;http://localhost:8080 -# in your .env file depending on your frontend port, 5173 in this case. -CORS_ALLOW_ORIGIN = os.environ.get('CORS_ALLOW_ORIGIN', '*').split(';') - -# Allows custom URL schemes (e.g., app://) to be used as origins for CORS. -# Useful for local development or desktop clients with schemes like app:// or other custom protocols. -# Provide a semicolon-separated list of allowed schemes in the environment variable CORS_ALLOW_CUSTOM_SCHEMES. -CORS_ALLOW_CUSTOM_SCHEME = os.environ.get('CORS_ALLOW_CUSTOM_SCHEME', '').split(';') - -if CORS_ALLOW_ORIGIN == ['*']: - log.warning("\n\nWARNING: CORS_ALLOW_ORIGIN IS SET TO '*' - NOT RECOMMENDED FOR PRODUCTION DEPLOYMENTS.\n") -else: - # You have to pick between a single wildcard or a list of origins. - # Doing both will result in CORS errors in the browser. - for origin in CORS_ALLOW_ORIGIN: - validate_cors_origin(origin) - - -class BannerModel(BaseModel): - id: str - type: str - title: Optional[str] = None - content: str - dismissible: bool - timestamp: int - - -try: - banners = json.loads(os.environ.get('WEBUI_BANNERS', '[]')) - banners = [BannerModel(**banner) for banner in banners] -except Exception as e: - log.exception(f'Error loading WEBUI_BANNERS: {e}') - banners = [] - -WEBUI_BANNERS = ConfigVar('WEBUI_BANNERS', 'ui.banners', banners) - - -SHOW_ADMIN_DETAILS = ConfigVar( - 'SHOW_ADMIN_DETAILS', - 'auth.admin.show', - os.environ.get('SHOW_ADMIN_DETAILS', 'true').lower() == 'true', -) - -ADMIN_EMAIL = ConfigVar( - 'ADMIN_EMAIL', - 'auth.admin.email', - os.environ.get('ADMIN_EMAIL', None), -) - - -#################################### -# TASKS -#################################### - - -TASK_MODEL = ConfigVar( - 'TASK_MODEL', - 'task.model.default', - os.environ.get('TASK_MODEL', ''), -) - -TASK_MODEL_EXTERNAL = ConfigVar( - 'TASK_MODEL_EXTERNAL', - 'task.model.external', - os.environ.get('TASK_MODEL_EXTERNAL', ''), -) - -TITLE_GENERATION_PROMPT_TEMPLATE = ConfigVar( - 'TITLE_GENERATION_PROMPT_TEMPLATE', - 'task.title.prompt_template', - os.environ.get('TITLE_GENERATION_PROMPT_TEMPLATE', ''), -) - -DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """### Task: -Generate a concise, 3-5 word title with an emoji summarizing the chat history. -### Guidelines: -- The title should clearly represent the main theme or subject of the conversation. -- Use emojis that enhance understanding of the topic, but avoid quotation marks or special formatting. -- Write the title in the chat's primary language; default to English if multilingual. -- Prioritize accuracy over excessive creativity; keep it clear and simple. -- Your entire response must consist solely of the JSON object, without any introductory or concluding text. -- The output must be a single, raw JSON object, without any markdown code fences or other encapsulating text. -- Ensure no conversational text, affirmations, or explanations precede or follow the raw JSON output, as this will cause direct parsing failure. -### Output: -JSON format: { "title": "your concise title here" } -### Examples: -- { "title": "๐Ÿ“‰ Stock Market Trends" }, -- { "title": "๐Ÿช Perfect Chocolate Chip Recipe" }, -- { "title": "Evolution of Music Streaming" }, -- { "title": "Remote Work Productivity Tips" }, -- { "title": "Artificial Intelligence in Healthcare" }, -- { "title": "๐ŸŽฎ Video Game Development Insights" } -### Chat History: - -{{MESSAGES:END:2}} -""" - -TAGS_GENERATION_PROMPT_TEMPLATE = ConfigVar( - 'TAGS_GENERATION_PROMPT_TEMPLATE', - 'task.tags.prompt_template', - os.environ.get('TAGS_GENERATION_PROMPT_TEMPLATE', ''), -) - -DEFAULT_TAGS_GENERATION_PROMPT_TEMPLATE = """### Task: -Generate 1-3 broad tags categorizing the main themes of the chat history, along with 1-3 more specific subtopic tags. - -### Guidelines: -- Start with high-level domains (e.g. Science, Technology, Philosophy, Arts, Politics, Business, Health, Sports, Entertainment, Education) -- Consider including relevant subfields/subdomains if they are strongly represented throughout the conversation -- If content is too short (less than 3 messages) or too diverse, use only ["General"] -- Use the chat's primary language; default to English if multilingual -- Prioritize accuracy over specificity - -### Output: -JSON format: { "tags": ["tag1", "tag2", "tag3"] } - -### Chat History: - -{{MESSAGES:END:6}} -""" - -IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = ConfigVar( - 'IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE', - 'task.image.prompt_template', - os.environ.get('IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE', ''), -) - -DEFAULT_IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = """### Task: -Generate a detailed prompt for am image generation task based on the given language and context. Describe the image as if you were explaining it to someone who cannot see it. Include relevant details, colors, shapes, and any other important elements. - -### Guidelines: -- Be descriptive and detailed, focusing on the most important aspects of the image. -- Avoid making assumptions or adding information not present in the image. -- Use the chat's primary language; default to English if multilingual. -- If the image is too complex, focus on the most prominent elements. - -### Output: -Strictly return in JSON format: -{ - "prompt": "Your detailed description here." -} - -### Chat History: - -{{MESSAGES:END:6}} -""" - - -FOLLOW_UP_GENERATION_PROMPT_TEMPLATE = ConfigVar( - 'FOLLOW_UP_GENERATION_PROMPT_TEMPLATE', - 'task.follow_up.prompt_template', - os.environ.get('FOLLOW_UP_GENERATION_PROMPT_TEMPLATE', ''), -) - -DEFAULT_FOLLOW_UP_GENERATION_PROMPT_TEMPLATE = """### Task: -Suggest 3-5 relevant follow-up questions or prompts that the user might naturally ask next in this conversation as a **user**, based on the chat history, to help continue or deepen the discussion. -### Guidelines: -- Write all follow-up questions from the userโ€™s point of view, directed to the assistant. -- Make questions concise, clear, and directly related to the discussed topic(s). -- Only suggest follow-ups that make sense given the chat content and do not repeat what was already covered. -- If the conversation is very short or not specific, suggest more general (but relevant) follow-ups the user might ask. -- Use the conversation's primary language; default to English if multilingual. -- Response must be a JSON object with a "follow_ups" key containing an array of strings, no extra text or formatting. -### Output: -JSON format: { "follow_ups": ["Question 1?", "Question 2?", "Question 3?"] } -### Chat History: - -{{MESSAGES:END:6}} -""" - -ENABLE_FOLLOW_UP_GENERATION = ConfigVar( - 'ENABLE_FOLLOW_UP_GENERATION', - 'task.follow_up.enable', - os.environ.get('ENABLE_FOLLOW_UP_GENERATION', 'True').lower() == 'true', -) - -ENABLE_TAGS_GENERATION = ConfigVar( - 'ENABLE_TAGS_GENERATION', - 'task.tags.enable', - os.environ.get('ENABLE_TAGS_GENERATION', 'True').lower() == 'true', -) - -ENABLE_TITLE_GENERATION = ConfigVar( - 'ENABLE_TITLE_GENERATION', - 'task.title.enable', - os.environ.get('ENABLE_TITLE_GENERATION', 'True').lower() == 'true', -) - - -ENABLE_SEARCH_QUERY_GENERATION = ConfigVar( - 'ENABLE_SEARCH_QUERY_GENERATION', - 'task.query.search.enable', - os.environ.get('ENABLE_SEARCH_QUERY_GENERATION', 'True').lower() == 'true', -) - -ENABLE_RETRIEVAL_QUERY_GENERATION = ConfigVar( - 'ENABLE_RETRIEVAL_QUERY_GENERATION', - 'task.query.retrieval.enable', - os.environ.get('ENABLE_RETRIEVAL_QUERY_GENERATION', 'True').lower() == 'true', -) - - -QUERY_GENERATION_PROMPT_TEMPLATE = ConfigVar( - 'QUERY_GENERATION_PROMPT_TEMPLATE', - 'task.query.prompt_template', - os.environ.get('QUERY_GENERATION_PROMPT_TEMPLATE', ''), -) - -DEFAULT_QUERY_GENERATION_PROMPT_TEMPLATE = """### Task: -Analyze the chat history to determine the necessity of generating search queries, in the given language. By default, **prioritize generating 1-3 broad and relevant search queries** unless it is absolutely certain that no additional information is required. The aim is to retrieve comprehensive, updated, and valuable information even with minimal uncertainty. If no search is unequivocally needed, return an empty list. - -### Guidelines: -- Respond **EXCLUSIVELY** with a JSON object. Any form of extra commentary, explanation, or additional text is strictly prohibited. -- When generating search queries, respond in the format: { "queries": ["query1", "query2"] }, ensuring each query is distinct, concise, and relevant to the topic. -- If and only if it is entirely certain that no useful results can be retrieved by a search, return: { "queries": [] }. -- Err on the side of suggesting search queries if there is **any chance** they might provide useful or updated information. -- Be concise and focused on composing high-quality search queries, avoiding unnecessary elaboration, commentary, or assumptions. -- Today's date is: {{CURRENT_DATE}}. -- Always prioritize providing actionable and broad queries that maximize informational coverage. - -### Output: -Strictly return in JSON format: -{ - "queries": ["query1", "query2"] -} - -### Chat History: - -{{MESSAGES:END:6}} - -""" - -ENABLE_AUTOCOMPLETE_GENERATION = ConfigVar( - 'ENABLE_AUTOCOMPLETE_GENERATION', - 'task.autocomplete.enable', - os.environ.get('ENABLE_AUTOCOMPLETE_GENERATION', 'False').lower() == 'true', -) - -AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH = ConfigVar( - 'AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH', - 'task.autocomplete.input_max_length', - int(os.environ.get('AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH', '-1')), -) - -AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE = ConfigVar( - 'AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE', - 'task.autocomplete.prompt_template', - os.environ.get('AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE', ''), -) - - -DEFAULT_AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE = """### Task: -You are an autocompletion system. Continue the text in `` based on the **completion type** in `` and the given language. - -### **Instructions**: -1. Analyze `` for context and meaning. -2. Use `` to guide your output: - - **General**: Provide a natural, concise continuation. - - **Search Query**: Complete as if generating a realistic search query. -3. Start as if you are directly continuing ``. Do **not** repeat, paraphrase, or respond as a model. Simply complete the text. -4. Ensure the continuation: - - Flows naturally from ``. - - Avoids repetition, overexplaining, or unrelated ideas. -5. If unsure, return: `{ "text": "" }`. - -### **Output Rules**: -- Respond only in JSON format: `{ "text": "" }`. - -### **Examples**: -#### Example 1: -Input: -General -The sun was setting over the horizon, painting the sky -Output: -{ "text": "with vibrant shades of orange and pink." } - -#### Example 2: -Input: -Search Query -Top-rated restaurants in -Output: -{ "text": "New York City for Italian cuisine." } - ---- -### Context: - -{{MESSAGES:END:6}} - -{{TYPE}} -{{PROMPT}} -#### Output: -""" - - -VOICE_MODE_PROMPT_TEMPLATE = ConfigVar( - 'VOICE_MODE_PROMPT_TEMPLATE', - 'task.voice.prompt_template', - os.environ.get('VOICE_MODE_PROMPT_TEMPLATE', ''), -) - -ENABLE_VOICE_MODE_PROMPT = ConfigVar( - 'ENABLE_VOICE_MODE_PROMPT', - 'task.voice.prompt.enable', - os.environ.get('ENABLE_VOICE_MODE_PROMPT', 'True').lower() == 'true', -) - -DEFAULT_VOICE_MODE_PROMPT_TEMPLATE = """You are a friendly, concise voice assistant. - -Everything you say will be spoken aloud. -Keep responses short, clear, and natural. - -STYLE: -- Use simple words and short sentences. -- Sound warm and conversational. -- Avoid long explanations, lists, or complex phrasing. - -BEHAVIOR: -- Give the quickest helpful answer first. -- Offer extra detail only if needed. -- Ask for clarification only when necessary. - -VOICE OPTIMIZATION: -- Break information into small, easy-to-hear chunks. -- Avoid dense wording or anything that sounds like reading text. - -ERROR HANDLING: -- If unsure, say so briefly and offer options. -- If something is unsafe or impossible, decline kindly and suggest a safe alternative. - -Stay consistent, helpful, and easy to listen to.""" - -TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = ConfigVar( - 'TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE', - 'task.tools.prompt_template', - os.environ.get('TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE', ''), -) - - -DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}} - -Your task is to choose and return the correct tool(s) from the list of available tools based on the query. Follow these guidelines: - -- Return only the JSON object, without any additional text or explanation. - -- If no tools match the query, return an empty array: - { - "tool_calls": [] - } - -- If one or more tools match the query, construct a JSON response containing a "tool_calls" array with objects that include: - - "name": The tool's name. - - "parameters": A dictionary of required parameters and their corresponding values. - -The format for the JSON response is strictly: -{ - "tool_calls": [ - {"name": "toolName1", "parameters": {"key1": "value1"}}, - {"name": "toolName2", "parameters": {"key2": "value2"}} - ] -}""" - - -DEFAULT_EMOJI_GENERATION_PROMPT_TEMPLATE = """Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., ๐Ÿ˜Š, ๐Ÿ˜ข, ๐Ÿ˜ก, ๐Ÿ˜ฑ). - -Message: ```{{prompt}}```""" - -DEFAULT_MOA_GENERATION_PROMPT_TEMPLATE = """You have been provided with a set of responses from various models to the latest user query: "{{prompt}}" - -Your task is to synthesize these responses into a single, high-quality response. It is crucial to critically evaluate the information provided in these responses, recognizing that some of it may be biased or incorrect. Your response should not simply replicate the given answers but should offer a refined, accurate, and comprehensive reply to the instruction. Ensure your response is well-structured, coherent, and adheres to the highest standards of accuracy and reliability. - -Responses from models: {{responses}}""" - - #################################### # Code Interpreter #################################### @@ -3921,6 +2446,1481 @@ AUDIO_TTS_MISTRAL_API_BASE_URL = ConfigVar( os.getenv('AUDIO_TTS_MISTRAL_API_BASE_URL', 'https://api.mistral.ai/v1'), ) +#################################### +# WEBUI +#################################### + + +WEBUI_URL = ConfigVar('WEBUI_URL', 'webui.url', os.environ.get('WEBUI_URL', '')) + + +ENABLE_SIGNUP = ConfigVar( + 'ENABLE_SIGNUP', + 'ui.enable_signup', + (False if not WEBUI_AUTH else os.environ.get('ENABLE_SIGNUP', 'True').lower() == 'true'), +) + +ENABLE_LOGIN_FORM = ConfigVar( + 'ENABLE_LOGIN_FORM', + 'ui.enable_login_form', + os.environ.get('ENABLE_LOGIN_FORM', 'True').lower() == 'true', +) + +ENABLE_PASSWORD_CHANGE_FORM = ConfigVar( + 'ENABLE_PASSWORD_CHANGE_FORM', + 'ui.enable_password_change_form', + os.environ.get('ENABLE_PASSWORD_CHANGE_FORM', 'True').lower() == 'true', +) + +ENABLE_PASSWORD_AUTH = os.environ.get('ENABLE_PASSWORD_AUTH', 'True').lower() == 'true' + +DEFAULT_LOCALE = ConfigVar( + 'DEFAULT_LOCALE', + 'ui.default_locale', + os.environ.get('DEFAULT_LOCALE', ''), +) + +DEFAULT_MODELS = ConfigVar('DEFAULT_MODELS', 'ui.default_models', os.environ.get('DEFAULT_MODELS', None)) + +DEFAULT_PINNED_MODELS = ConfigVar( + 'DEFAULT_PINNED_MODELS', + 'ui.default_pinned_models', + os.environ.get('DEFAULT_PINNED_MODELS', None), +) + +try: + default_prompt_suggestions = json.loads(os.environ.get('DEFAULT_PROMPT_SUGGESTIONS', '[]')) +except Exception as e: + log.exception(f'Error loading DEFAULT_PROMPT_SUGGESTIONS: {e}') + default_prompt_suggestions = [] +if default_prompt_suggestions == []: + default_prompt_suggestions = [ + { + 'title': ['Help me study', 'vocabulary for a college entrance exam'], + 'content': "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.", + }, + { + 'title': ['Give me ideas', "for what to do with my kids' art"], + 'content': "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.", + }, + { + 'title': ['Tell me a fun fact', 'about the Roman Empire'], + 'content': 'Tell me a random fun fact about the Roman Empire', + }, + { + 'title': ['Show me a code snippet', "of a website's sticky header"], + 'content': "Show me a code snippet of a website's sticky header in CSS and JavaScript.", + }, + { + 'title': [ + 'Explain options trading', + "if I'm familiar with buying and selling stocks", + ], + 'content': "Explain options trading in simple terms if I'm familiar with buying and selling stocks.", + }, + { + 'title': ['Overcome procrastination', 'give me tips'], + 'content': 'Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?', + }, + ] + +DEFAULT_PROMPT_SUGGESTIONS = ConfigVar( + 'DEFAULT_PROMPT_SUGGESTIONS', + 'ui.prompt_suggestions', + default_prompt_suggestions, +) + +MODEL_ORDER_LIST = ConfigVar( + 'MODEL_ORDER_LIST', + 'ui.model_order_list', + [], +) + +try: + default_model_metadata = json.loads(os.environ.get('DEFAULT_MODEL_METADATA', '{}')) +except Exception as e: + log.exception(f'Error loading DEFAULT_MODEL_METADATA: {e}') + default_model_metadata = {} + +DEFAULT_MODEL_METADATA = ConfigVar( + 'DEFAULT_MODEL_METADATA', + 'models.default_metadata', + default_model_metadata, +) + +try: + default_model_params = json.loads(os.environ.get('DEFAULT_MODEL_PARAMS', '{}')) +except Exception as e: + log.exception(f'Error loading DEFAULT_MODEL_PARAMS: {e}') + default_model_params = {} + +DEFAULT_MODEL_PARAMS = ConfigVar( + 'DEFAULT_MODEL_PARAMS', + 'models.default_params', + default_model_params, +) + +DEFAULT_USER_ROLE = ConfigVar( + 'DEFAULT_USER_ROLE', + 'ui.default_user_role', + os.getenv('DEFAULT_USER_ROLE', 'pending'), +) + +DEFAULT_GROUP_ID = ConfigVar( + 'DEFAULT_GROUP_ID', + 'ui.default_group_id', + os.environ.get('DEFAULT_GROUP_ID', ''), +) + +PENDING_USER_OVERLAY_TITLE = ConfigVar( + 'PENDING_USER_OVERLAY_TITLE', + 'ui.pending_user_overlay_title', + os.environ.get('PENDING_USER_OVERLAY_TITLE', ''), +) + +PENDING_USER_OVERLAY_CONTENT = ConfigVar( + 'PENDING_USER_OVERLAY_CONTENT', + 'ui.pending_user_overlay_content', + os.environ.get('PENDING_USER_OVERLAY_CONTENT', ''), +) + + +RESPONSE_WATERMARK = ConfigVar( + 'RESPONSE_WATERMARK', + 'ui.watermark', + os.environ.get('RESPONSE_WATERMARK', ''), +) + +IFRAME_CSP = os.environ.get('IFRAME_CSP', '') + +USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT', 'False').lower() == 'true' +) + + +USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' +) + + +USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING = ( + os.environ.get('USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' +) + + +USER_PERMISSIONS_NOTES_ALLOW_SHARING = os.environ.get('USER_PERMISSIONS_NOTES_ALLOW_SHARING', 'False').lower() == 'true' + +USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING = ( + os.environ.get('USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING = ( + os.environ.get('USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS = ( + os.environ.get('USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS', 'True').lower() == 'true' +) + + +USER_PERMISSIONS_CHAT_CONTROLS = os.environ.get('USER_PERMISSIONS_CHAT_CONTROLS', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_VALVES = os.environ.get('USER_PERMISSIONS_CHAT_VALVES', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_SYSTEM_PROMPT = os.environ.get('USER_PERMISSIONS_CHAT_SYSTEM_PROMPT', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_PARAMS = os.environ.get('USER_PERMISSIONS_CHAT_PARAMS', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_FILE_UPLOAD = os.environ.get('USER_PERMISSIONS_CHAT_FILE_UPLOAD', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_WEB_UPLOAD = os.environ.get('USER_PERMISSIONS_CHAT_WEB_UPLOAD', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_DELETE = os.environ.get('USER_PERMISSIONS_CHAT_DELETE', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_DELETE_MESSAGE = os.environ.get('USER_PERMISSIONS_CHAT_DELETE_MESSAGE', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE = ( + os.environ.get('USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE', 'True').lower() == 'true' +) + +USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE = ( + os.environ.get('USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE', 'True').lower() == 'true' +) + +USER_PERMISSIONS_CHAT_RATE_RESPONSE = os.environ.get('USER_PERMISSIONS_CHAT_RATE_RESPONSE', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_EDIT = os.environ.get('USER_PERMISSIONS_CHAT_EDIT', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_SHARE = os.environ.get('USER_PERMISSIONS_CHAT_SHARE', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING = ( + os.environ.get('USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true' +) + +USER_PERMISSIONS_CHAT_EXPORT = os.environ.get('USER_PERMISSIONS_CHAT_EXPORT', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_STT = os.environ.get('USER_PERMISSIONS_CHAT_STT', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_TTS = os.environ.get('USER_PERMISSIONS_CHAT_TTS', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_CALL = os.environ.get('USER_PERMISSIONS_CHAT_CALL', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_MULTIPLE_MODELS = ( + os.environ.get('USER_PERMISSIONS_CHAT_MULTIPLE_MODELS', 'True').lower() == 'true' +) + +USER_PERMISSIONS_CHAT_TEMPORARY = os.environ.get('USER_PERMISSIONS_CHAT_TEMPORARY', 'True').lower() == 'true' + +USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED = ( + os.environ.get('USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED', 'False').lower() == 'true' +) + + +USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS = ( + os.environ.get('USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS', 'False').lower() == 'true' +) + +USER_PERMISSIONS_FEATURES_WEB_SEARCH = os.environ.get('USER_PERMISSIONS_FEATURES_WEB_SEARCH', 'True').lower() == 'true' + +USER_PERMISSIONS_FEATURES_IMAGE_GENERATION = ( + os.environ.get('USER_PERMISSIONS_FEATURES_IMAGE_GENERATION', 'True').lower() == 'true' +) + +USER_PERMISSIONS_FEATURES_CODE_INTERPRETER = ( + os.environ.get('USER_PERMISSIONS_FEATURES_CODE_INTERPRETER', 'True').lower() == 'true' +) + +USER_PERMISSIONS_FEATURES_FOLDERS = os.environ.get('USER_PERMISSIONS_FEATURES_FOLDERS', 'True').lower() == 'true' + +USER_PERMISSIONS_FEATURES_NOTES = os.environ.get('USER_PERMISSIONS_FEATURES_NOTES', 'True').lower() == 'true' + +USER_PERMISSIONS_FEATURES_CHANNELS = os.environ.get('USER_PERMISSIONS_FEATURES_CHANNELS', 'True').lower() == 'true' + +USER_PERMISSIONS_FEATURES_API_KEYS = os.environ.get('USER_PERMISSIONS_FEATURES_API_KEYS', 'False').lower() == 'true' + +USER_PERMISSIONS_FEATURES_MEMORIES = os.environ.get('USER_PERMISSIONS_FEATURES_MEMORIES', 'True').lower() == 'true' + +USER_PERMISSIONS_FEATURES_AUTOMATIONS = ( + os.environ.get('USER_PERMISSIONS_FEATURES_AUTOMATIONS', 'False').lower() == 'true' +) + +USER_PERMISSIONS_FEATURES_CALENDAR = os.environ.get('USER_PERMISSIONS_FEATURES_CALENDAR', 'True').lower() == 'true' + + +USER_PERMISSIONS_SETTINGS_INTERFACE = os.environ.get('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true' + + +DEFAULT_USER_PERMISSIONS = { + 'workspace': { + 'models': USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS, + 'knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS, + 'prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS, + 'tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS, + 'skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS, + 'models_import': USER_PERMISSIONS_WORKSPACE_MODELS_IMPORT, + 'models_export': USER_PERMISSIONS_WORKSPACE_MODELS_EXPORT, + 'prompts_import': USER_PERMISSIONS_WORKSPACE_PROMPTS_IMPORT, + 'prompts_export': USER_PERMISSIONS_WORKSPACE_PROMPTS_EXPORT, + 'tools_import': USER_PERMISSIONS_WORKSPACE_TOOLS_IMPORT, + 'tools_export': USER_PERMISSIONS_WORKSPACE_TOOLS_EXPORT, + }, + 'sharing': { + 'models': USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_SHARING, + 'public_models': USER_PERMISSIONS_WORKSPACE_MODELS_ALLOW_PUBLIC_SHARING, + 'knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_SHARING, + 'public_knowledge': USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING, + 'prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_SHARING, + 'public_prompts': USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING, + 'tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING, + 'public_tools': USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING, + 'skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING, + 'public_skills': USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING, + 'notes': USER_PERMISSIONS_NOTES_ALLOW_SHARING, + 'public_notes': USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING, + 'public_chats': USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING, + 'public_calendars': USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING, + }, + 'access_grants': { + 'allow_users': USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS, + }, + 'chat': { + 'controls': USER_PERMISSIONS_CHAT_CONTROLS, + 'valves': USER_PERMISSIONS_CHAT_VALVES, + 'system_prompt': USER_PERMISSIONS_CHAT_SYSTEM_PROMPT, + 'params': USER_PERMISSIONS_CHAT_PARAMS, + 'file_upload': USER_PERMISSIONS_CHAT_FILE_UPLOAD, + 'web_upload': USER_PERMISSIONS_CHAT_WEB_UPLOAD, + 'delete': USER_PERMISSIONS_CHAT_DELETE, + 'delete_message': USER_PERMISSIONS_CHAT_DELETE_MESSAGE, + 'continue_response': USER_PERMISSIONS_CHAT_CONTINUE_RESPONSE, + 'regenerate_response': USER_PERMISSIONS_CHAT_REGENERATE_RESPONSE, + 'rate_response': USER_PERMISSIONS_CHAT_RATE_RESPONSE, + 'edit': USER_PERMISSIONS_CHAT_EDIT, + 'share': USER_PERMISSIONS_CHAT_SHARE, + 'export': USER_PERMISSIONS_CHAT_EXPORT, + 'stt': USER_PERMISSIONS_CHAT_STT, + 'tts': USER_PERMISSIONS_CHAT_TTS, + 'call': USER_PERMISSIONS_CHAT_CALL, + 'multiple_models': USER_PERMISSIONS_CHAT_MULTIPLE_MODELS, + 'temporary': USER_PERMISSIONS_CHAT_TEMPORARY, + 'temporary_enforced': USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED, + }, + 'features': { + # General features + 'api_keys': USER_PERMISSIONS_FEATURES_API_KEYS, + 'notes': USER_PERMISSIONS_FEATURES_NOTES, + 'folders': USER_PERMISSIONS_FEATURES_FOLDERS, + 'channels': USER_PERMISSIONS_FEATURES_CHANNELS, + 'direct_tool_servers': USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS, + # Chat features + 'web_search': USER_PERMISSIONS_FEATURES_WEB_SEARCH, + 'image_generation': USER_PERMISSIONS_FEATURES_IMAGE_GENERATION, + 'code_interpreter': USER_PERMISSIONS_FEATURES_CODE_INTERPRETER, + 'memories': USER_PERMISSIONS_FEATURES_MEMORIES, + 'automations': USER_PERMISSIONS_FEATURES_AUTOMATIONS, + 'calendar': USER_PERMISSIONS_FEATURES_CALENDAR, + }, + 'settings': { + 'interface': USER_PERMISSIONS_SETTINGS_INTERFACE, + }, +} + +USER_PERMISSIONS = ConfigVar( + 'USER_PERMISSIONS', + 'user.permissions', + DEFAULT_USER_PERMISSIONS, +) + +ENABLE_FOLDERS = ConfigVar( + 'ENABLE_FOLDERS', + 'folders.enable', + os.environ.get('ENABLE_FOLDERS', 'True').lower() == 'true', +) + +FOLDER_MAX_FILE_COUNT = ConfigVar( + 'FOLDER_MAX_FILE_COUNT', + 'folders.max_file_count', + os.environ.get('FOLDER_MAX_FILE_COUNT', ''), +) + +ENABLE_CHANNELS = ConfigVar( + 'ENABLE_CHANNELS', + 'channels.enable', + os.environ.get('ENABLE_CHANNELS', 'False').lower() == 'true', +) + +ENABLE_CALENDAR = ConfigVar( + 'ENABLE_CALENDAR', + 'calendar.enable', + os.environ.get('ENABLE_CALENDAR', 'True').lower() == 'true', +) + +ENABLE_AUTOMATIONS = ConfigVar( + 'ENABLE_AUTOMATIONS', + 'automations.enable', + os.environ.get('ENABLE_AUTOMATIONS', 'True').lower() == 'true', +) + +AUTOMATION_MAX_COUNT = ConfigVar( + 'AUTOMATION_MAX_COUNT', + 'automations.max_count', + os.environ.get('AUTOMATION_MAX_COUNT', ''), +) + +AUTOMATION_MIN_INTERVAL = ConfigVar( + 'AUTOMATION_MIN_INTERVAL', + 'automations.min_interval', + os.environ.get('AUTOMATION_MIN_INTERVAL', ''), +) + +ENABLE_NOTES = ConfigVar( + 'ENABLE_NOTES', + 'notes.enable', + os.environ.get('ENABLE_NOTES', 'True').lower() == 'true', +) + +ENABLE_USER_STATUS = ConfigVar( + 'ENABLE_USER_STATUS', + 'users.enable_status', + os.environ.get('ENABLE_USER_STATUS', 'True').lower() == 'true', +) + +ENABLE_EVALUATION_ARENA_MODELS = ConfigVar( + 'ENABLE_EVALUATION_ARENA_MODELS', + 'evaluation.arena.enable', + os.environ.get('ENABLE_EVALUATION_ARENA_MODELS', 'True').lower() == 'true', +) +EVALUATION_ARENA_MODELS = ConfigVar( + 'EVALUATION_ARENA_MODELS', + 'evaluation.arena.models', + [], +) + +DEFAULT_ARENA_MODEL = { + 'id': 'arena-model', + 'name': 'Arena Model', + 'meta': { + 'profile_image_url': '/favicon.png', + 'description': 'Submit your questions to anonymous AI chatbots and vote on the best response.', + 'model_ids': None, + }, +} + +WEBHOOK_URL = ConfigVar('WEBHOOK_URL', 'webhook_url', os.environ.get('WEBHOOK_URL', '')) + +ENABLE_ADMIN_EXPORT = os.environ.get('ENABLE_ADMIN_EXPORT', 'True').lower() == 'true' + +ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS = ( + os.environ.get('ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS', 'True').lower() == 'true' +) + +BYPASS_ADMIN_ACCESS_CONTROL = ( + os.environ.get( + 'BYPASS_ADMIN_ACCESS_CONTROL', + os.environ.get('ENABLE_ADMIN_WORKSPACE_CONTENT_ACCESS', 'True'), + ).lower() + == 'true' +) + +ENABLE_ADMIN_CHAT_ACCESS = os.environ.get('ENABLE_ADMIN_CHAT_ACCESS', 'True').lower() == 'true' + +ENABLE_ADMIN_ANALYTICS = os.environ.get('ENABLE_ADMIN_ANALYTICS', 'True').lower() == 'true' + +ENABLE_COMMUNITY_SHARING = ConfigVar( + 'ENABLE_COMMUNITY_SHARING', + 'ui.enable_community_sharing', + os.environ.get('ENABLE_COMMUNITY_SHARING', 'True').lower() == 'true', +) + +ENABLE_MESSAGE_RATING = ConfigVar( + 'ENABLE_MESSAGE_RATING', + 'ui.enable_message_rating', + os.environ.get('ENABLE_MESSAGE_RATING', 'True').lower() == 'true', +) + +ENABLE_USER_WEBHOOKS = ConfigVar( + 'ENABLE_USER_WEBHOOKS', + 'ui.enable_user_webhooks', + os.environ.get('ENABLE_USER_WEBHOOKS', 'False').lower() == 'true', +) + +# FastAPI / AnyIO settings +THREAD_POOL_SIZE = os.getenv('THREAD_POOL_SIZE', None) + +if THREAD_POOL_SIZE is not None and isinstance(THREAD_POOL_SIZE, str): + try: + THREAD_POOL_SIZE = int(THREAD_POOL_SIZE) + except ValueError: + log.warning(f'THREAD_POOL_SIZE is not a valid integer: {THREAD_POOL_SIZE}. Defaulting to None.') + THREAD_POOL_SIZE = None + + +def validate_cors_origin(origin): + parsed_url = urlparse(origin) + + # Check if the scheme is either http or https, or a custom scheme + schemes = ['http', 'https'] + CORS_ALLOW_CUSTOM_SCHEME + if parsed_url.scheme not in schemes: + raise ValueError( + f"Invalid scheme in CORS_ALLOW_ORIGIN: '{origin}'. Only 'http' and 'https' and CORS_ALLOW_CUSTOM_SCHEME are allowed." + ) + + # Ensure that the netloc (domain + port) is present, indicating it's a valid URL + if not parsed_url.netloc: + raise ValueError(f"Invalid URL structure in CORS_ALLOW_ORIGIN: '{origin}'.") + + +# For production, you should only need one host as +# fastapi serves the svelte-kit built frontend and backend from the same host and port. +# To test CORS_ALLOW_ORIGIN locally, you can set something like +# CORS_ALLOW_ORIGIN=http://localhost:5173;http://localhost:8080 +# in your .env file depending on your frontend port, 5173 in this case. +CORS_ALLOW_ORIGIN = os.environ.get('CORS_ALLOW_ORIGIN', '*').split(';') + +# Allows custom URL schemes (e.g., app://) to be used as origins for CORS. +# Useful for local development or desktop clients with schemes like app:// or other custom protocols. +# Provide a semicolon-separated list of allowed schemes in the environment variable CORS_ALLOW_CUSTOM_SCHEMES. +CORS_ALLOW_CUSTOM_SCHEME = os.environ.get('CORS_ALLOW_CUSTOM_SCHEME', '').split(';') + +if CORS_ALLOW_ORIGIN == ['*']: + log.warning("\n\nWARNING: CORS_ALLOW_ORIGIN IS SET TO '*' - NOT RECOMMENDED FOR PRODUCTION DEPLOYMENTS.\n") +else: + # You have to pick between a single wildcard or a list of origins. + # Doing both will result in CORS errors in the browser. + for origin in CORS_ALLOW_ORIGIN: + validate_cors_origin(origin) + + +class BannerModel(BaseModel): + id: str + type: str + title: Optional[str] = None + content: str + dismissible: bool + timestamp: int + + +try: + banners = json.loads(os.environ.get('WEBUI_BANNERS', '[]')) + banners = [BannerModel(**banner) for banner in banners] +except Exception as e: + log.exception(f'Error loading WEBUI_BANNERS: {e}') + banners = [] + +WEBUI_BANNERS = ConfigVar('WEBUI_BANNERS', 'ui.banners', banners) + + +SHOW_ADMIN_DETAILS = ConfigVar( + 'SHOW_ADMIN_DETAILS', + 'auth.admin.show', + os.environ.get('SHOW_ADMIN_DETAILS', 'true').lower() == 'true', +) + +ADMIN_EMAIL = ConfigVar( + 'ADMIN_EMAIL', + 'auth.admin.email', + os.environ.get('ADMIN_EMAIL', None), +) + + +#################################### +# TASKS +#################################### + + +TASK_MODEL = ConfigVar( + 'TASK_MODEL', + 'task.model.default', + os.environ.get('TASK_MODEL', ''), +) + +TASK_MODEL_EXTERNAL = ConfigVar( + 'TASK_MODEL_EXTERNAL', + 'task.model.external', + os.environ.get('TASK_MODEL_EXTERNAL', ''), +) + +TITLE_GENERATION_PROMPT_TEMPLATE = ConfigVar( + 'TITLE_GENERATION_PROMPT_TEMPLATE', + 'task.title.prompt_template', + os.environ.get('TITLE_GENERATION_PROMPT_TEMPLATE', ''), +) + +DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """### Task: +Generate a concise, 3-5 word title with an emoji summarizing the chat history. +### Guidelines: +- The title should clearly represent the main theme or subject of the conversation. +- Use emojis that enhance understanding of the topic, but avoid quotation marks or special formatting. +- Write the title in the chat's primary language; default to English if multilingual. +- Prioritize accuracy over excessive creativity; keep it clear and simple. +- Your entire response must consist solely of the JSON object, without any introductory or concluding text. +- The output must be a single, raw JSON object, without any markdown code fences or other encapsulating text. +- Ensure no conversational text, affirmations, or explanations precede or follow the raw JSON output, as this will cause direct parsing failure. +### Output: +JSON format: { "title": "your concise title here" } +### Examples: +- { "title": "๐Ÿ“‰ Stock Market Trends" }, +- { "title": "๐Ÿช Perfect Chocolate Chip Recipe" }, +- { "title": "Evolution of Music Streaming" }, +- { "title": "Remote Work Productivity Tips" }, +- { "title": "Artificial Intelligence in Healthcare" }, +- { "title": "๐ŸŽฎ Video Game Development Insights" } +### Chat History: + +{{MESSAGES:END:2}} +""" + +TAGS_GENERATION_PROMPT_TEMPLATE = ConfigVar( + 'TAGS_GENERATION_PROMPT_TEMPLATE', + 'task.tags.prompt_template', + os.environ.get('TAGS_GENERATION_PROMPT_TEMPLATE', ''), +) + +DEFAULT_TAGS_GENERATION_PROMPT_TEMPLATE = """### Task: +Generate 1-3 broad tags categorizing the main themes of the chat history, along with 1-3 more specific subtopic tags. + +### Guidelines: +- Start with high-level domains (e.g. Science, Technology, Philosophy, Arts, Politics, Business, Health, Sports, Entertainment, Education) +- Consider including relevant subfields/subdomains if they are strongly represented throughout the conversation +- If content is too short (less than 3 messages) or too diverse, use only ["General"] +- Use the chat's primary language; default to English if multilingual +- Prioritize accuracy over specificity + +### Output: +JSON format: { "tags": ["tag1", "tag2", "tag3"] } + +### Chat History: + +{{MESSAGES:END:6}} +""" + +IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = ConfigVar( + 'IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE', + 'task.image.prompt_template', + os.environ.get('IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE', ''), +) + +DEFAULT_IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = """### Task: +Generate a detailed prompt for am image generation task based on the given language and context. Describe the image as if you were explaining it to someone who cannot see it. Include relevant details, colors, shapes, and any other important elements. + +### Guidelines: +- Be descriptive and detailed, focusing on the most important aspects of the image. +- Avoid making assumptions or adding information not present in the image. +- Use the chat's primary language; default to English if multilingual. +- If the image is too complex, focus on the most prominent elements. + +### Output: +Strictly return in JSON format: +{ + "prompt": "Your detailed description here." +} + +### Chat History: + +{{MESSAGES:END:6}} +""" + + +FOLLOW_UP_GENERATION_PROMPT_TEMPLATE = ConfigVar( + 'FOLLOW_UP_GENERATION_PROMPT_TEMPLATE', + 'task.follow_up.prompt_template', + os.environ.get('FOLLOW_UP_GENERATION_PROMPT_TEMPLATE', ''), +) + +DEFAULT_FOLLOW_UP_GENERATION_PROMPT_TEMPLATE = """### Task: +Suggest 3-5 relevant follow-up questions or prompts that the user might naturally ask next in this conversation as a **user**, based on the chat history, to help continue or deepen the discussion. +### Guidelines: +- Write all follow-up questions from the userโ€™s point of view, directed to the assistant. +- Make questions concise, clear, and directly related to the discussed topic(s). +- Only suggest follow-ups that make sense given the chat content and do not repeat what was already covered. +- If the conversation is very short or not specific, suggest more general (but relevant) follow-ups the user might ask. +- Use the conversation's primary language; default to English if multilingual. +- Response must be a JSON object with a "follow_ups" key containing an array of strings, no extra text or formatting. +### Output: +JSON format: { "follow_ups": ["Question 1?", "Question 2?", "Question 3?"] } +### Chat History: + +{{MESSAGES:END:6}} +""" + +ENABLE_FOLLOW_UP_GENERATION = ConfigVar( + 'ENABLE_FOLLOW_UP_GENERATION', + 'task.follow_up.enable', + os.environ.get('ENABLE_FOLLOW_UP_GENERATION', 'True').lower() == 'true', +) + +ENABLE_TAGS_GENERATION = ConfigVar( + 'ENABLE_TAGS_GENERATION', + 'task.tags.enable', + os.environ.get('ENABLE_TAGS_GENERATION', 'True').lower() == 'true', +) + +ENABLE_TITLE_GENERATION = ConfigVar( + 'ENABLE_TITLE_GENERATION', + 'task.title.enable', + os.environ.get('ENABLE_TITLE_GENERATION', 'True').lower() == 'true', +) + + +ENABLE_SEARCH_QUERY_GENERATION = ConfigVar( + 'ENABLE_SEARCH_QUERY_GENERATION', + 'task.query.search.enable', + os.environ.get('ENABLE_SEARCH_QUERY_GENERATION', 'True').lower() == 'true', +) + +ENABLE_RETRIEVAL_QUERY_GENERATION = ConfigVar( + 'ENABLE_RETRIEVAL_QUERY_GENERATION', + 'task.query.retrieval.enable', + os.environ.get('ENABLE_RETRIEVAL_QUERY_GENERATION', 'True').lower() == 'true', +) + + +QUERY_GENERATION_PROMPT_TEMPLATE = ConfigVar( + 'QUERY_GENERATION_PROMPT_TEMPLATE', + 'task.query.prompt_template', + os.environ.get('QUERY_GENERATION_PROMPT_TEMPLATE', ''), +) + +DEFAULT_QUERY_GENERATION_PROMPT_TEMPLATE = """### Task: +Analyze the chat history to determine the necessity of generating search queries, in the given language. By default, **prioritize generating 1-3 broad and relevant search queries** unless it is absolutely certain that no additional information is required. The aim is to retrieve comprehensive, updated, and valuable information even with minimal uncertainty. If no search is unequivocally needed, return an empty list. + +### Guidelines: +- Respond **EXCLUSIVELY** with a JSON object. Any form of extra commentary, explanation, or additional text is strictly prohibited. +- When generating search queries, respond in the format: { "queries": ["query1", "query2"] }, ensuring each query is distinct, concise, and relevant to the topic. +- If and only if it is entirely certain that no useful results can be retrieved by a search, return: { "queries": [] }. +- Err on the side of suggesting search queries if there is **any chance** they might provide useful or updated information. +- Be concise and focused on composing high-quality search queries, avoiding unnecessary elaboration, commentary, or assumptions. +- Today's date is: {{CURRENT_DATE}}. +- Always prioritize providing actionable and broad queries that maximize informational coverage. + +### Output: +Strictly return in JSON format: +{ + "queries": ["query1", "query2"] +} + +### Chat History: + +{{MESSAGES:END:6}} + +""" + +ENABLE_AUTOCOMPLETE_GENERATION = ConfigVar( + 'ENABLE_AUTOCOMPLETE_GENERATION', + 'task.autocomplete.enable', + os.environ.get('ENABLE_AUTOCOMPLETE_GENERATION', 'False').lower() == 'true', +) + +AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH = ConfigVar( + 'AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH', + 'task.autocomplete.input_max_length', + int(os.environ.get('AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH', '-1')), +) + +AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE = ConfigVar( + 'AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE', + 'task.autocomplete.prompt_template', + os.environ.get('AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE', ''), +) + + +DEFAULT_AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE = """### Task: +You are an autocompletion system. Continue the text in `` based on the **completion type** in `` and the given language. + +### **Instructions**: +1. Analyze `` for context and meaning. +2. Use `` to guide your output: + - **General**: Provide a natural, concise continuation. + - **Search Query**: Complete as if generating a realistic search query. +3. Start as if you are directly continuing ``. Do **not** repeat, paraphrase, or respond as a model. Simply complete the text. +4. Ensure the continuation: + - Flows naturally from ``. + - Avoids repetition, overexplaining, or unrelated ideas. +5. If unsure, return: `{ "text": "" }`. + +### **Output Rules**: +- Respond only in JSON format: `{ "text": "" }`. + +### **Examples**: +#### Example 1: +Input: +General +The sun was setting over the horizon, painting the sky +Output: +{ "text": "with vibrant shades of orange and pink." } + +#### Example 2: +Input: +Search Query +Top-rated restaurants in +Output: +{ "text": "New York City for Italian cuisine." } + +--- +### Context: + +{{MESSAGES:END:6}} + +{{TYPE}} +{{PROMPT}} +#### Output: +""" + + +VOICE_MODE_PROMPT_TEMPLATE = ConfigVar( + 'VOICE_MODE_PROMPT_TEMPLATE', + 'task.voice.prompt_template', + os.environ.get('VOICE_MODE_PROMPT_TEMPLATE', ''), +) + +ENABLE_VOICE_MODE_PROMPT = ConfigVar( + 'ENABLE_VOICE_MODE_PROMPT', + 'task.voice.prompt.enable', + os.environ.get('ENABLE_VOICE_MODE_PROMPT', 'True').lower() == 'true', +) + +DEFAULT_VOICE_MODE_PROMPT_TEMPLATE = """You are a friendly, concise voice assistant. + +Everything you say will be spoken aloud. +Keep responses short, clear, and natural. + +STYLE: +- Use simple words and short sentences. +- Sound warm and conversational. +- Avoid long explanations, lists, or complex phrasing. + +BEHAVIOR: +- Give the quickest helpful answer first. +- Offer extra detail only if needed. +- Ask for clarification only when necessary. + +VOICE OPTIMIZATION: +- Break information into small, easy-to-hear chunks. +- Avoid dense wording or anything that sounds like reading text. + +ERROR HANDLING: +- If unsure, say so briefly and offer options. +- If something is unsafe or impossible, decline kindly and suggest a safe alternative. + +Stay consistent, helpful, and easy to listen to.""" + +TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = ConfigVar( + 'TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE', + 'task.tools.prompt_template', + os.environ.get('TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE', ''), +) + + +DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}} + +Your task is to choose and return the correct tool(s) from the list of available tools based on the query. Follow these guidelines: + +- Return only the JSON object, without any additional text or explanation. + +- If no tools match the query, return an empty array: + { + "tool_calls": [] + } + +- If one or more tools match the query, construct a JSON response containing a "tool_calls" array with objects that include: + - "name": The tool's name. + - "parameters": A dictionary of required parameters and their corresponding values. + +The format for the JSON response is strictly: +{ + "tool_calls": [ + {"name": "toolName1", "parameters": {"key1": "value1"}}, + {"name": "toolName2", "parameters": {"key2": "value2"}} + ] +}""" + + +DEFAULT_EMOJI_GENERATION_PROMPT_TEMPLATE = """Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., ๐Ÿ˜Š, ๐Ÿ˜ข, ๐Ÿ˜ก, ๐Ÿ˜ฑ). + +Message: ```{{prompt}}```""" + +DEFAULT_MOA_GENERATION_PROMPT_TEMPLATE = """You have been provided with a set of responses from various models to the latest user query: "{{prompt}}" + +Your task is to synthesize these responses into a single, high-quality response. It is crucial to critically evaluate the information provided in these responses, recognizing that some of it may be biased or incorrect. Your response should not simply replicate the given answers but should offer a refined, accurate, and comprehensive reply to the instruction. Ensure your response is well-structured, coherent, and adheres to the highest standards of accuracy and reliability. + +Responses from models: {{responses}}""" + + +#################################### +# Auth +#################################### + +ENABLE_API_KEYS = ConfigVar( + 'ENABLE_API_KEYS', + 'auth.enable_api_keys', + os.environ.get('ENABLE_API_KEYS', 'False').lower() == 'true', +) + +ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = ConfigVar( + 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS', + 'auth.api_key.endpoint_restrictions', + os.environ.get( + 'ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS', + os.environ.get('ENABLE_API_KEY_ENDPOINT_RESTRICTIONS', 'False'), + ).lower() + == 'true', +) + +API_KEYS_ALLOWED_ENDPOINTS = ConfigVar( + 'API_KEYS_ALLOWED_ENDPOINTS', + 'auth.api_key.allowed_endpoints', + os.environ.get('API_KEYS_ALLOWED_ENDPOINTS', os.environ.get('API_KEY_ALLOWED_ENDPOINTS', '')), +) + +JWT_EXPIRES_IN = ConfigVar('JWT_EXPIRES_IN', 'auth.jwt_expiry', os.environ.get('JWT_EXPIRES_IN', '4w')) + +if JWT_EXPIRES_IN.value == '-1': + log.warning( + "โš ๏ธ SECURITY WARNING: JWT_EXPIRES_IN is set to '-1'\n" + ' See: https://docs.openwebui.com/reference/env-configuration\n' + ) + +#################################### +# OAuth config +#################################### + +ENABLE_OAUTH_SIGNUP = ConfigVar( + 'ENABLE_OAUTH_SIGNUP', + 'oauth.enable_signup', + os.environ.get('ENABLE_OAUTH_SIGNUP', 'False').lower() == 'true', +) + +OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE = ConfigVar( + 'OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE', + 'oauth.refresh_token_include_scope', + os.environ.get('OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE', 'False').lower() == 'true', +) + + +OAUTH_MERGE_ACCOUNTS_BY_EMAIL = ConfigVar( + 'OAUTH_MERGE_ACCOUNTS_BY_EMAIL', + 'oauth.merge_accounts_by_email', + os.environ.get('OAUTH_MERGE_ACCOUNTS_BY_EMAIL', 'False').lower() == 'true', +) + +OAUTH_PROVIDERS = {} + +GOOGLE_CLIENT_ID = ConfigVar( + 'GOOGLE_CLIENT_ID', + 'oauth.google.client_id', + os.environ.get('GOOGLE_CLIENT_ID', ''), +) + +GOOGLE_CLIENT_SECRET = ConfigVar( + 'GOOGLE_CLIENT_SECRET', + 'oauth.google.client_secret', + os.environ.get('GOOGLE_CLIENT_SECRET', ''), +) + + +GOOGLE_OAUTH_SCOPE = ConfigVar( + 'GOOGLE_OAUTH_SCOPE', + 'oauth.google.scope', + os.environ.get('GOOGLE_OAUTH_SCOPE', 'openid email profile'), +) + +GOOGLE_REDIRECT_URI = ConfigVar( + 'GOOGLE_REDIRECT_URI', + 'oauth.google.redirect_uri', + os.environ.get('GOOGLE_REDIRECT_URI', ''), +) + +GOOGLE_OAUTH_AUTHORIZE_PARAMS = {} +_google_oauth_authorize_params = os.environ.get('GOOGLE_OAUTH_AUTHORIZE_PARAMS', '') +if _google_oauth_authorize_params: + try: + _parsed = json.loads(_google_oauth_authorize_params) + if isinstance(_parsed, dict): + GOOGLE_OAUTH_AUTHORIZE_PARAMS = _parsed + else: + log.warning('GOOGLE_OAUTH_AUTHORIZE_PARAMS must be a JSON object, ignoring') + except (json.JSONDecodeError, TypeError): + log.warning('GOOGLE_OAUTH_AUTHORIZE_PARAMS is not valid JSON, ignoring') + +MICROSOFT_CLIENT_ID = ConfigVar( + 'MICROSOFT_CLIENT_ID', + 'oauth.microsoft.client_id', + os.environ.get('MICROSOFT_CLIENT_ID', ''), +) + +MICROSOFT_CLIENT_SECRET = ConfigVar( + 'MICROSOFT_CLIENT_SECRET', + 'oauth.microsoft.client_secret', + os.environ.get('MICROSOFT_CLIENT_SECRET', ''), +) + +MICROSOFT_CLIENT_TENANT_ID = ConfigVar( + 'MICROSOFT_CLIENT_TENANT_ID', + 'oauth.microsoft.tenant_id', + os.environ.get('MICROSOFT_CLIENT_TENANT_ID', ''), +) + +MICROSOFT_CLIENT_LOGIN_BASE_URL = ConfigVar( + 'MICROSOFT_CLIENT_LOGIN_BASE_URL', + 'oauth.microsoft.login_base_url', + os.environ.get('MICROSOFT_CLIENT_LOGIN_BASE_URL', 'https://login.microsoftonline.com'), +) + +MICROSOFT_CLIENT_PICTURE_URL = ConfigVar( + 'MICROSOFT_CLIENT_PICTURE_URL', + 'oauth.microsoft.picture_url', + os.environ.get( + 'MICROSOFT_CLIENT_PICTURE_URL', + 'https://graph.microsoft.com/v1.0/me/photo/$value', + ), +) + + +MICROSOFT_OAUTH_SCOPE = ConfigVar( + 'MICROSOFT_OAUTH_SCOPE', + 'oauth.microsoft.scope', + os.environ.get('MICROSOFT_OAUTH_SCOPE', 'openid email profile'), +) + +MICROSOFT_REDIRECT_URI = ConfigVar( + 'MICROSOFT_REDIRECT_URI', + 'oauth.microsoft.redirect_uri', + os.environ.get('MICROSOFT_REDIRECT_URI', ''), +) + +GITHUB_CLIENT_ID = ConfigVar( + 'GITHUB_CLIENT_ID', + 'oauth.github.client_id', + os.environ.get('GITHUB_CLIENT_ID', ''), +) + +GITHUB_CLIENT_SECRET = ConfigVar( + 'GITHUB_CLIENT_SECRET', + 'oauth.github.client_secret', + os.environ.get('GITHUB_CLIENT_SECRET', ''), +) + +GITHUB_CLIENT_SCOPE = ConfigVar( + 'GITHUB_CLIENT_SCOPE', + 'oauth.github.scope', + os.environ.get('GITHUB_CLIENT_SCOPE', 'user:email'), +) + +GITHUB_CLIENT_REDIRECT_URI = ConfigVar( + 'GITHUB_CLIENT_REDIRECT_URI', + 'oauth.github.redirect_uri', + os.environ.get('GITHUB_CLIENT_REDIRECT_URI', ''), +) + +OAUTH_CLIENT_ID = ConfigVar( + 'OAUTH_CLIENT_ID', + 'oauth.oidc.client_id', + os.environ.get('OAUTH_CLIENT_ID', ''), +) + +OAUTH_CLIENT_SECRET = ConfigVar( + 'OAUTH_CLIENT_SECRET', + 'oauth.oidc.client_secret', + os.environ.get('OAUTH_CLIENT_SECRET', ''), +) + +OPENID_PROVIDER_URL = ConfigVar( + 'OPENID_PROVIDER_URL', + 'oauth.oidc.provider_url', + os.environ.get('OPENID_PROVIDER_URL', ''), +) + +OPENID_END_SESSION_ENDPOINT = ConfigVar( + 'OPENID_END_SESSION_ENDPOINT', + 'oauth.oidc.end_session_endpoint', + os.environ.get('OPENID_END_SESSION_ENDPOINT', ''), +) + +OPENID_REDIRECT_URI = ConfigVar( + 'OPENID_REDIRECT_URI', + 'oauth.oidc.redirect_uri', + os.environ.get('OPENID_REDIRECT_URI', ''), +) + +OAUTH_SCOPES = ConfigVar( + 'OAUTH_SCOPES', + 'oauth.oidc.scopes', + os.environ.get('OAUTH_SCOPES', 'openid email profile'), +) + +OAUTH_TIMEOUT = ConfigVar( + 'OAUTH_TIMEOUT', + 'oauth.oidc.oauth_timeout', + os.environ.get('OAUTH_TIMEOUT', ''), +) + +OAUTH_TOKEN_ENDPOINT_AUTH_METHOD = ConfigVar( + 'OAUTH_TOKEN_ENDPOINT_AUTH_METHOD', + 'oauth.oidc.token_endpoint_auth_method', + os.environ.get('OAUTH_TOKEN_ENDPOINT_AUTH_METHOD', None), +) + +OAUTH_CODE_CHALLENGE_METHOD = ConfigVar( + 'OAUTH_CODE_CHALLENGE_METHOD', + 'oauth.oidc.code_challenge_method', + os.environ.get('OAUTH_CODE_CHALLENGE_METHOD', None), +) + +OAUTH_PROVIDER_NAME = ConfigVar( + 'OAUTH_PROVIDER_NAME', + 'oauth.oidc.provider_name', + os.environ.get('OAUTH_PROVIDER_NAME', 'SSO'), +) + +OAUTH_SUB_CLAIM = ConfigVar( + 'OAUTH_SUB_CLAIM', + 'oauth.oidc.sub_claim', + os.environ.get('OAUTH_SUB_CLAIM', None), +) + +OAUTH_USERNAME_CLAIM = ConfigVar( + 'OAUTH_USERNAME_CLAIM', + 'oauth.oidc.username_claim', + os.environ.get('OAUTH_USERNAME_CLAIM', 'name'), +) + + +OAUTH_PICTURE_CLAIM = ConfigVar( + 'OAUTH_PICTURE_CLAIM', + 'oauth.oidc.avatar_claim', + os.environ.get('OAUTH_PICTURE_CLAIM', 'picture'), +) + +OAUTH_EMAIL_CLAIM = ConfigVar( + 'OAUTH_EMAIL_CLAIM', + 'oauth.oidc.email_claim', + os.environ.get('OAUTH_EMAIL_CLAIM', 'email'), +) + +OAUTH_GROUPS_CLAIM = ConfigVar( + 'OAUTH_GROUPS_CLAIM', + 'oauth.oidc.group_claim', + os.environ.get('OAUTH_GROUPS_CLAIM', os.environ.get('OAUTH_GROUP_CLAIM', 'groups')), +) + +FEISHU_CLIENT_ID = ConfigVar( + 'FEISHU_CLIENT_ID', + 'oauth.feishu.client_id', + os.environ.get('FEISHU_CLIENT_ID', ''), +) + +FEISHU_CLIENT_SECRET = ConfigVar( + 'FEISHU_CLIENT_SECRET', + 'oauth.feishu.client_secret', + os.environ.get('FEISHU_CLIENT_SECRET', ''), +) + +FEISHU_OAUTH_SCOPE = ConfigVar( + 'FEISHU_OAUTH_SCOPE', + 'oauth.feishu.scope', + os.environ.get('FEISHU_OAUTH_SCOPE', 'contact:user.base:readonly'), +) + +FEISHU_REDIRECT_URI = ConfigVar( + 'FEISHU_REDIRECT_URI', + 'oauth.feishu.redirect_uri', + os.environ.get('FEISHU_REDIRECT_URI', ''), +) + +ENABLE_OAUTH_ROLE_MANAGEMENT = ConfigVar( + 'ENABLE_OAUTH_ROLE_MANAGEMENT', + 'oauth.enable_role_mapping', + os.environ.get('ENABLE_OAUTH_ROLE_MANAGEMENT', 'False').lower() == 'true', +) + +ENABLE_OAUTH_GROUP_MANAGEMENT = ConfigVar( + 'ENABLE_OAUTH_GROUP_MANAGEMENT', + 'oauth.enable_group_mapping', + os.environ.get('ENABLE_OAUTH_GROUP_MANAGEMENT', 'False').lower() == 'true', +) + +ENABLE_OAUTH_GROUP_CREATION = ConfigVar( + 'ENABLE_OAUTH_GROUP_CREATION', + 'oauth.enable_group_creation', + os.environ.get('ENABLE_OAUTH_GROUP_CREATION', 'False').lower() == 'true', +) + + +oauth_group_default_share = os.environ.get('OAUTH_GROUP_DEFAULT_SHARE', 'true').strip().lower() +OAUTH_GROUP_DEFAULT_SHARE = ConfigVar( + 'OAUTH_GROUP_DEFAULT_SHARE', + 'oauth.group_default_share', + ('members' if oauth_group_default_share == 'members' else oauth_group_default_share == 'true'), +) + + +OAUTH_BLOCKED_GROUPS = ConfigVar( + 'OAUTH_BLOCKED_GROUPS', + 'oauth.blocked_groups', + os.environ.get('OAUTH_BLOCKED_GROUPS', '[]'), +) + +OAUTH_GROUPS_SEPARATOR = os.environ.get('OAUTH_GROUPS_SEPARATOR', ';') + +OAUTH_ROLES_CLAIM = ConfigVar( + 'OAUTH_ROLES_CLAIM', + 'oauth.roles_claim', + os.environ.get('OAUTH_ROLES_CLAIM', 'roles'), +) + +OAUTH_ROLES_SEPARATOR = os.environ.get('OAUTH_ROLES_SEPARATOR', ',') + +OAUTH_ALLOWED_ROLES = ConfigVar( + 'OAUTH_ALLOWED_ROLES', + 'oauth.allowed_roles', + [ + role.strip() + for role in os.environ.get('OAUTH_ALLOWED_ROLES', f'user{OAUTH_ROLES_SEPARATOR}admin').split( + OAUTH_ROLES_SEPARATOR + ) + if role + ], +) + +OAUTH_ADMIN_ROLES = ConfigVar( + 'OAUTH_ADMIN_ROLES', + 'oauth.admin_roles', + [role.strip() for role in os.environ.get('OAUTH_ADMIN_ROLES', 'admin').split(OAUTH_ROLES_SEPARATOR) if role], +) + +OAUTH_ALLOWED_DOMAINS = ConfigVar( + 'OAUTH_ALLOWED_DOMAINS', + 'oauth.allowed_domains', + [domain.strip() for domain in os.environ.get('OAUTH_ALLOWED_DOMAINS', '*').split(',')], +) + +OAUTH_UPDATE_PICTURE_ON_LOGIN = ConfigVar( + 'OAUTH_UPDATE_PICTURE_ON_LOGIN', + 'oauth.update_picture_on_login', + os.environ.get('OAUTH_UPDATE_PICTURE_ON_LOGIN', 'False').lower() == 'true', +) + +OAUTH_UPDATE_NAME_ON_LOGIN = ConfigVar( + 'OAUTH_UPDATE_NAME_ON_LOGIN', + 'oauth.update_name_on_login', + os.environ.get('OAUTH_UPDATE_NAME_ON_LOGIN', 'False').lower() == 'true', +) + +OAUTH_UPDATE_EMAIL_ON_LOGIN = ConfigVar( + 'OAUTH_UPDATE_EMAIL_ON_LOGIN', + 'oauth.update_email_on_login', + os.environ.get('OAUTH_UPDATE_EMAIL_ON_LOGIN', 'False').lower() == 'true', +) + +OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID = ( + os.environ.get('OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID', 'False').lower() == 'true' +) + +OAUTH_AUDIENCE = ConfigVar( + 'OAUTH_AUDIENCE', + 'oauth.audience', + os.environ.get('OAUTH_AUDIENCE', ''), +) + +OAUTH_AUTHORIZE_PARAMS = {} +_oauth_authorize_params = os.environ.get('OAUTH_AUTHORIZE_PARAMS', '') +if _oauth_authorize_params: + try: + _parsed = json.loads(_oauth_authorize_params) + if isinstance(_parsed, dict): + OAUTH_AUTHORIZE_PARAMS = _parsed + else: + log.warning('OAUTH_AUTHORIZE_PARAMS must be a JSON object, ignoring') + except (json.JSONDecodeError, TypeError): + log.warning('OAUTH_AUTHORIZE_PARAMS is not valid JSON, ignoring') + + +def load_oauth_providers(): + OAUTH_PROVIDERS.clear() + if GOOGLE_CLIENT_ID.value and GOOGLE_CLIENT_SECRET.value: + + def google_oauth_register(oauth: OAuth): + client = oauth.register( + name='google', + client_id=GOOGLE_CLIENT_ID.value, + client_secret=GOOGLE_CLIENT_SECRET.value, + server_metadata_url='https://accounts.google.com/.well-known/openid-configuration', + client_kwargs={ + 'scope': GOOGLE_OAUTH_SCOPE.value, + **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), + }, + redirect_uri=GOOGLE_REDIRECT_URI.value, + **({'authorize_params': GOOGLE_OAUTH_AUTHORIZE_PARAMS} if GOOGLE_OAUTH_AUTHORIZE_PARAMS else {}), + ) + return client + + OAUTH_PROVIDERS['google'] = { + 'register': google_oauth_register, + } + + if MICROSOFT_CLIENT_ID.value and MICROSOFT_CLIENT_SECRET.value and MICROSOFT_CLIENT_TENANT_ID.value: + + def microsoft_oauth_register(oauth: OAuth): + client = oauth.register( + name='microsoft', + client_id=MICROSOFT_CLIENT_ID.value, + client_secret=MICROSOFT_CLIENT_SECRET.value, + server_metadata_url=f'{MICROSOFT_CLIENT_LOGIN_BASE_URL.value}/{MICROSOFT_CLIENT_TENANT_ID.value}/v2.0/.well-known/openid-configuration?appid={MICROSOFT_CLIENT_ID.value}', + client_kwargs={ + 'scope': MICROSOFT_OAUTH_SCOPE.value, + **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), + }, + redirect_uri=MICROSOFT_REDIRECT_URI.value, + ) + return client + + OAUTH_PROVIDERS['microsoft'] = { + 'picture_url': MICROSOFT_CLIENT_PICTURE_URL.value, + 'register': microsoft_oauth_register, + } + + if GITHUB_CLIENT_ID.value and GITHUB_CLIENT_SECRET.value: + + def github_oauth_register(oauth: OAuth): + client = oauth.register( + name='github', + client_id=GITHUB_CLIENT_ID.value, + client_secret=GITHUB_CLIENT_SECRET.value, + access_token_url='https://github.com/login/oauth/access_token', + authorize_url='https://github.com/login/oauth/authorize', + api_base_url='https://api.github.com', + userinfo_endpoint='https://api.github.com/user', + client_kwargs={ + 'scope': GITHUB_CLIENT_SCOPE.value, + **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), + }, + redirect_uri=GITHUB_CLIENT_REDIRECT_URI.value, + ) + return client + + OAUTH_PROVIDERS['github'] = { + 'register': github_oauth_register, + 'sub_claim': 'id', + } + + if ( + OAUTH_CLIENT_ID.value + and (OAUTH_CLIENT_SECRET.value or OAUTH_CODE_CHALLENGE_METHOD.value) + and OPENID_PROVIDER_URL.value + ): + + def oidc_oauth_register(oauth: OAuth): + client_kwargs = { + 'scope': OAUTH_SCOPES.value, + **( + {'token_endpoint_auth_method': OAUTH_TOKEN_ENDPOINT_AUTH_METHOD.value} + if OAUTH_TOKEN_ENDPOINT_AUTH_METHOD.value + else {} + ), + **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), + } + + if OAUTH_CODE_CHALLENGE_METHOD.value and OAUTH_CODE_CHALLENGE_METHOD.value == 'S256': + client_kwargs['code_challenge_method'] = 'S256' + elif OAUTH_CODE_CHALLENGE_METHOD.value: + raise Exception( + 'Code challenge methods other than "%s" not supported. Given: "%s"' + % ('S256', OAUTH_CODE_CHALLENGE_METHOD.value) + ) + + client = oauth.register( + name='oidc', + client_id=OAUTH_CLIENT_ID.value, + client_secret=OAUTH_CLIENT_SECRET.value, + server_metadata_url=OPENID_PROVIDER_URL.value, + client_kwargs=client_kwargs, + redirect_uri=OPENID_REDIRECT_URI.value, + ) + return client + + OAUTH_PROVIDERS['oidc'] = { + 'name': OAUTH_PROVIDER_NAME.value, + 'register': oidc_oauth_register, + } + + if FEISHU_CLIENT_ID.value and FEISHU_CLIENT_SECRET.value: + + def feishu_oauth_register(oauth: OAuth): + client = oauth.register( + name='feishu', + client_id=FEISHU_CLIENT_ID.value, + client_secret=FEISHU_CLIENT_SECRET.value, + access_token_url='https://open.feishu.cn/open-apis/authen/v2/oauth/token', + authorize_url='https://accounts.feishu.cn/open-apis/authen/v1/authorize', + api_base_url='https://open.feishu.cn/open-apis', + userinfo_endpoint='https://open.feishu.cn/open-apis/authen/v1/user_info', + client_kwargs={ + 'scope': FEISHU_OAUTH_SCOPE.value, + **({'timeout': int(OAUTH_TIMEOUT.value)} if OAUTH_TIMEOUT.value else {}), + }, + redirect_uri=FEISHU_REDIRECT_URI.value, + ) + return client + + OAUTH_PROVIDERS['feishu'] = { + 'register': feishu_oauth_register, + 'sub_claim': 'user_id', + } + + configured_providers = [] + if GOOGLE_CLIENT_ID.value: + configured_providers.append('Google') + if MICROSOFT_CLIENT_ID.value: + configured_providers.append('Microsoft') + if GITHUB_CLIENT_ID.value: + configured_providers.append('GitHub') + if FEISHU_CLIENT_ID.value: + configured_providers.append('Feishu') + + if configured_providers and not OPENID_PROVIDER_URL.value and not OPENID_END_SESSION_ENDPOINT.value: + provider_list = ', '.join(configured_providers) + log.warning( + f'โš ๏ธ OAuth providers configured ({provider_list}) but OPENID_PROVIDER_URL not set - logout will not work!' + ) + log.warning( + f"Set OPENID_PROVIDER_URL to your OAuth provider's OpenID Connect discovery endpoint," + f' or set OPENID_END_SESSION_ENDPOINT to a custom logout URL to fix logout functionality.' + ) + + +load_oauth_providers() #################################### # LDAP @@ -3998,7 +3998,6 @@ LDAP_VALIDATE_CERT = ConfigVar( LDAP_CIPHERS = ConfigVar('LDAP_CIPHERS', 'ldap.server.ciphers', os.environ.get('LDAP_CIPHERS', 'ALL')) -# For LDAP Group Management ENABLE_LDAP_GROUP_MANAGEMENT = ConfigVar( 'ENABLE_LDAP_GROUP_MANAGEMENT', 'ldap.group.enable_management', diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 903b3effcc..2b9c532087 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -9,7 +9,7 @@ import shutil import sys import traceback from pathlib import Path -from typing import Any +from typing import Any, Optional from uuid import uuid4 import markdown @@ -43,27 +43,27 @@ except ImportError: DOCKER = os.environ.get('DOCKER', 'False').lower() == 'true' -# device type for embedding models - "cpu" (default), "cuda" (nvidia gpu required), or "mps" (apple silicon) -# choosing this correctly can lead to better performance USE_CUDA = os.environ.get('USE_CUDA_DOCKER', 'false') +DEVICE_TYPE = 'cpu' +_cuda_error: Optional[str] = None if USE_CUDA.lower() == 'true': try: - import torch + import torch # noqa: E402 - assert torch.cuda.is_available(), 'CUDA not available' + if not torch.cuda.is_available(): + raise RuntimeError('CUDA not available') DEVICE_TYPE = 'cuda' - except Exception as e: - cuda_error = f'Error when testing CUDA but USE_CUDA_DOCKER is true. Resetting USE_CUDA_DOCKER to false: {e}' + except Exception as exc: + _cuda_error = ( + f'CUDA unavailable (USE_CUDA_DOCKER=true), falling back to CPU: {exc}' + ) os.environ['USE_CUDA_DOCKER'] = 'false' USE_CUDA = 'false' - DEVICE_TYPE = 'cpu' -else: - DEVICE_TYPE = 'cpu' -if sys.platform == 'darwin': +if sys.platform == 'darwin' and DEVICE_TYPE == 'cpu': try: - import torch + import torch # noqa: E402 if torch.backends.mps.is_available() and torch.backends.mps.is_built(): DEVICE_TYPE = 'mps' @@ -109,32 +109,26 @@ LOG_FORMAT = os.environ.get('LOG_FORMAT', '').lower() GLOBAL_LOG_LEVEL = os.environ.get('GLOBAL_LOG_LEVEL', '').upper() if GLOBAL_LOG_LEVEL in logging.getLevelNamesMapping(): + _log_cfg: dict[str, Any] = {'level': GLOBAL_LOG_LEVEL, 'force': True} if LOG_FORMAT == 'json': - _handler = logging.StreamHandler(sys.stdout) - _handler.setFormatter(JSONFormatter()) - logging.basicConfig(handlers=[_handler], level=GLOBAL_LOG_LEVEL, force=True) + _json_handler = logging.StreamHandler(sys.stdout) + _json_handler.setFormatter(JSONFormatter()) + _log_cfg['handlers'] = [_json_handler] else: - logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True) + _log_cfg['stream'] = sys.stdout + logging.basicConfig(**_log_cfg) else: GLOBAL_LOG_LEVEL = 'INFO' log = logging.getLogger(__name__) -log.info(f'GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}') +log.info('GLOBAL_LOG_LEVEL: %s', GLOBAL_LOG_LEVEL) -if 'cuda_error' in locals(): - log.exception(cuda_error) - del cuda_error +if _cuda_error: + log.error(_cuda_error) + _cuda_error = None SRC_LOG_LEVELS = {} # Legacy variable, do not remove -WEBUI_NAME = os.environ.get('WEBUI_NAME', 'Open WebUI') -if WEBUI_NAME != 'Open WebUI': - WEBUI_NAME += ' (Open WebUI)' - -WEBUI_FAVICON_URL = 'https://openwebui.com/favicon.png' - -TRUSTED_SIGNATURE_KEY = os.environ.get('TRUSTED_SIGNATURE_KEY', '') - #################################### # ENV (dev,test,prod) #################################### @@ -219,62 +213,6 @@ for version in soup.find_all('h2'): CHANGELOG = changelog_json -#################################### -# SAFE_MODE -#################################### - -SAFE_MODE = os.environ.get('SAFE_MODE', 'false').lower() == 'true' - - -#################################### -# ENABLE_FORWARD_USER_INFO_HEADERS -#################################### - -ENABLE_FORWARD_USER_INFO_HEADERS = os.environ.get('ENABLE_FORWARD_USER_INFO_HEADERS', 'False').lower() == 'true' - -# Header names for user info forwarding (customizable via environment variables) -FORWARD_USER_INFO_HEADER_USER_NAME = os.environ.get('FORWARD_USER_INFO_HEADER_USER_NAME', 'X-OpenWebUI-User-Name') -FORWARD_USER_INFO_HEADER_USER_ID = os.environ.get('FORWARD_USER_INFO_HEADER_USER_ID', 'X-OpenWebUI-User-Id') -FORWARD_USER_INFO_HEADER_USER_EMAIL = os.environ.get('FORWARD_USER_INFO_HEADER_USER_EMAIL', 'X-OpenWebUI-User-Email') -FORWARD_USER_INFO_HEADER_USER_ROLE = os.environ.get('FORWARD_USER_INFO_HEADER_USER_ROLE', 'X-OpenWebUI-User-Role') - -# Header name for chat ID forwarding (customizable via environment variable) -FORWARD_SESSION_INFO_HEADER_MESSAGE_ID = os.environ.get( - 'FORWARD_SESSION_INFO_HEADER_MESSAGE_ID', 'X-OpenWebUI-Message-Id' -) -FORWARD_SESSION_INFO_HEADER_CHAT_ID = os.environ.get('FORWARD_SESSION_INFO_HEADER_CHAT_ID', 'X-OpenWebUI-Chat-Id') - -# Experimental feature, may be removed in future -ENABLE_STAR_SESSIONS_MIDDLEWARE = os.environ.get('ENABLE_STAR_SESSIONS_MIDDLEWARE', 'False').lower() == 'true' - -ENABLE_EASTER_EGGS = os.environ.get('ENABLE_EASTER_EGGS', 'True').lower() == 'true' - -#################################### -# ENABLE_PROFILE_IMAGE_URL_FORWARDING -#################################### - -# When True (default), the user and model profile-image endpoints -# honour external http(s) URLs stored in profile_image_url by issuing a -# 302 redirect to the original origin. Set to False to suppress the -# redirect (prevents client-side IP/UA/Referer leaks to attacker- -# controlled origins) and fall through to the default image instead. -ENABLE_PROFILE_IMAGE_URL_FORWARDING = os.environ.get('ENABLE_PROFILE_IMAGE_URL_FORWARDING', 'True').lower() == 'true' - -PROFILE_IMAGE_ALLOWED_MIME_TYPES = frozenset( - t.strip() - for t in os.environ.get( - 'PROFILE_IMAGE_ALLOWED_MIME_TYPES', - 'image/png,image/jpeg,image/gif,image/webp', - ).split(',') - if t.strip() -) - -#################################### -# WEBUI_BUILD_HASH -#################################### - -WEBUI_BUILD_HASH = os.environ.get('WEBUI_BUILD_HASH', 'dev-build') - #################################### # DATA/FRONTEND BUILD DIR #################################### @@ -358,43 +296,29 @@ if 'postgres://' in DATABASE_URL: DATABASE_SCHEMA = os.environ.get('DATABASE_SCHEMA', None) -DATABASE_POOL_SIZE = os.environ.get('DATABASE_POOL_SIZE', None) +_pool_size_raw = os.environ.get('DATABASE_POOL_SIZE') +try: + DATABASE_POOL_SIZE = int(_pool_size_raw) if _pool_size_raw else None +except (ValueError, TypeError): + DATABASE_POOL_SIZE = None -if DATABASE_POOL_SIZE is not None: - try: - DATABASE_POOL_SIZE = int(DATABASE_POOL_SIZE) - except Exception: - DATABASE_POOL_SIZE = None - -DATABASE_POOL_MAX_OVERFLOW = os.environ.get('DATABASE_POOL_MAX_OVERFLOW', 0) - -if DATABASE_POOL_MAX_OVERFLOW == '': +_pool_overflow_raw = os.environ.get('DATABASE_POOL_MAX_OVERFLOW', '0') +try: + DATABASE_POOL_MAX_OVERFLOW = int(_pool_overflow_raw) if _pool_overflow_raw else 0 +except (ValueError, TypeError): DATABASE_POOL_MAX_OVERFLOW = 0 -else: - try: - DATABASE_POOL_MAX_OVERFLOW = int(DATABASE_POOL_MAX_OVERFLOW) - except Exception: - DATABASE_POOL_MAX_OVERFLOW = 0 -DATABASE_POOL_TIMEOUT = os.environ.get('DATABASE_POOL_TIMEOUT', 30) - -if DATABASE_POOL_TIMEOUT == '': +_pool_timeout_raw = os.environ.get('DATABASE_POOL_TIMEOUT', '30') +try: + DATABASE_POOL_TIMEOUT = int(_pool_timeout_raw) if _pool_timeout_raw else 30 +except (ValueError, TypeError): DATABASE_POOL_TIMEOUT = 30 -else: - try: - DATABASE_POOL_TIMEOUT = int(DATABASE_POOL_TIMEOUT) - except Exception: - DATABASE_POOL_TIMEOUT = 30 -DATABASE_POOL_RECYCLE = os.environ.get('DATABASE_POOL_RECYCLE', 3600) - -if DATABASE_POOL_RECYCLE == '': +_pool_recycle_raw = os.environ.get('DATABASE_POOL_RECYCLE', '3600') +try: + DATABASE_POOL_RECYCLE = int(_pool_recycle_raw) if _pool_recycle_raw else 3600 +except (ValueError, TypeError): DATABASE_POOL_RECYCLE = 3600 -else: - try: - DATABASE_POOL_RECYCLE = int(DATABASE_POOL_RECYCLE) - except Exception: - DATABASE_POOL_RECYCLE = 3600 DATABASE_ENABLE_SQLITE_WAL = os.environ.get('DATABASE_ENABLE_SQLITE_WAL', 'True').lower() == 'true' @@ -433,18 +357,11 @@ if DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL is not None: except Exception: DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = 0.0 -# When enabled, get_db_context reuses existing sessions; set to False to always create new sessions DATABASE_ENABLE_SESSION_SHARING = os.environ.get('DATABASE_ENABLE_SESSION_SHARING', 'False').lower() == 'true' - -# Enable public visibility of active user count (when disabled, only admins can see it) ENABLE_PUBLIC_ACTIVE_USERS_COUNT = os.environ.get('ENABLE_PUBLIC_ACTIVE_USERS_COUNT', 'True').lower() == 'true' - RESET_CONFIG_ON_START = os.environ.get('RESET_CONFIG_ON_START', 'False').lower() == 'true' - ENABLE_REALTIME_CHAT_SAVE = os.environ.get('ENABLE_REALTIME_CHAT_SAVE', 'False').lower() == 'true' - ENABLE_QUERIES_CACHE = os.environ.get('ENABLE_QUERIES_CACHE', 'False').lower() == 'true' - RAG_SYSTEM_CONTEXT = os.environ.get('RAG_SYSTEM_CONTEXT', 'False').lower() == 'true' #################################### @@ -509,21 +426,170 @@ else: REDIS_RECONNECT_DELAY = None #################################### -# UVICORN WORKERS +# Uvicorn #################################### -# Number of uvicorn worker processes for handling requests -UVICORN_WORKERS = os.environ.get('UVICORN_WORKERS', '1') try: - UVICORN_WORKERS = int(UVICORN_WORKERS) - if UVICORN_WORKERS < 1: - UVICORN_WORKERS = 1 -except ValueError: + UVICORN_WORKERS = max(int(os.environ.get('UVICORN_WORKERS', '1')), 1) +except (ValueError, TypeError): UVICORN_WORKERS = 1 - log.info(f'Invalid UVICORN_WORKERS value, defaulting to {UVICORN_WORKERS}') #################################### -# WEBUI_AUTH (Required for security) +# WEBSOCKET SUPPORT +#################################### + +ENABLE_WEBSOCKET_SUPPORT = os.environ.get('ENABLE_WEBSOCKET_SUPPORT', 'True').lower() == 'true' + + +WEBSOCKET_MANAGER = os.environ.get('WEBSOCKET_MANAGER', '') + +WEBSOCKET_REDIS_OPTIONS = os.environ.get('WEBSOCKET_REDIS_OPTIONS', '') + + +if WEBSOCKET_REDIS_OPTIONS == '': + if REDIS_SOCKET_CONNECT_TIMEOUT: + WEBSOCKET_REDIS_OPTIONS = {'socket_connect_timeout': REDIS_SOCKET_CONNECT_TIMEOUT} + else: + log.debug('No WEBSOCKET_REDIS_OPTIONS provided, defaulting to None') + WEBSOCKET_REDIS_OPTIONS = None +else: + try: + WEBSOCKET_REDIS_OPTIONS = json.loads(WEBSOCKET_REDIS_OPTIONS) + except Exception: + log.warning('Invalid WEBSOCKET_REDIS_OPTIONS, defaulting to None') + WEBSOCKET_REDIS_OPTIONS = None + +WEBSOCKET_REDIS_URL = os.environ.get('WEBSOCKET_REDIS_URL', REDIS_URL) +WEBSOCKET_REDIS_CLUSTER = os.environ.get('WEBSOCKET_REDIS_CLUSTER', str(REDIS_CLUSTER)).lower() == 'true' + +websocket_redis_lock_timeout = os.environ.get('WEBSOCKET_REDIS_LOCK_TIMEOUT', '60') + +try: + WEBSOCKET_REDIS_LOCK_TIMEOUT = int(websocket_redis_lock_timeout) +except ValueError: + WEBSOCKET_REDIS_LOCK_TIMEOUT = 60 + +WEBSOCKET_SENTINEL_HOSTS = os.environ.get('WEBSOCKET_SENTINEL_HOSTS', '') +WEBSOCKET_SENTINEL_PORT = os.environ.get('WEBSOCKET_SENTINEL_PORT', '26379') +WEBSOCKET_SERVER_LOGGING = os.environ.get('WEBSOCKET_SERVER_LOGGING', 'False').lower() == 'true' +WEBSOCKET_SERVER_ENGINEIO_LOGGING = ( + os.environ.get( + 'WEBSOCKET_SERVER_ENGINEIO_LOGGING', + os.environ.get('WEBSOCKET_SERVER_LOGGING', 'False'), + ).lower() + == 'true' +) +WEBSOCKET_SERVER_PING_TIMEOUT = os.environ.get('WEBSOCKET_SERVER_PING_TIMEOUT', '20') +try: + WEBSOCKET_SERVER_PING_TIMEOUT = int(WEBSOCKET_SERVER_PING_TIMEOUT) +except ValueError: + WEBSOCKET_SERVER_PING_TIMEOUT = 20 + +WEBSOCKET_SERVER_PING_INTERVAL = os.environ.get('WEBSOCKET_SERVER_PING_INTERVAL', '25') +try: + WEBSOCKET_SERVER_PING_INTERVAL = int(WEBSOCKET_SERVER_PING_INTERVAL) +except ValueError: + WEBSOCKET_SERVER_PING_INTERVAL = 25 + +WEBSOCKET_EVENT_CALLER_TIMEOUT = os.environ.get('WEBSOCKET_EVENT_CALLER_TIMEOUT', '') + +if WEBSOCKET_EVENT_CALLER_TIMEOUT == '': + WEBSOCKET_EVENT_CALLER_TIMEOUT = None +else: + try: + WEBSOCKET_EVENT_CALLER_TIMEOUT = int(WEBSOCKET_EVENT_CALLER_TIMEOUT) + except ValueError: + WEBSOCKET_EVENT_CALLER_TIMEOUT = 300 + + +REQUESTS_VERIFY = os.environ.get('REQUESTS_VERIFY', 'True').lower() == 'true' + +_aiohttp_timeout_raw = os.environ.get('AIOHTTP_CLIENT_TIMEOUT', '') +try: + AIOHTTP_CLIENT_TIMEOUT = int(_aiohttp_timeout_raw) if _aiohttp_timeout_raw else None +except (ValueError, TypeError): + AIOHTTP_CLIENT_TIMEOUT = 300 + + +AIOHTTP_CLIENT_SESSION_SSL = os.environ.get('AIOHTTP_CLIENT_SESSION_SSL', 'True').lower() == 'true' + +# When False (default), outbound HTTP requests do not follow 3xx redirects. +AIOHTTP_CLIENT_ALLOW_REDIRECTS = os.environ.get('AIOHTTP_CLIENT_ALLOW_REDIRECTS', 'False').lower() == 'true' + +_model_list_timeout_raw = os.environ.get( + 'AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST', + os.environ.get('AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST', '10'), +) +try: + AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = int(_model_list_timeout_raw) if _model_list_timeout_raw else None +except (ValueError, TypeError): + AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = 10 + +_tool_data_timeout_raw = os.environ.get('AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA', '10') +try: + AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = int(_tool_data_timeout_raw) if _tool_data_timeout_raw else None +except (ValueError, TypeError): + AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = 10 + + +AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = ( + os.environ.get('AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL', 'True').lower() == 'true' +) + +AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = os.environ.get('AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER', '') + +if AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER == '': + AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = AIOHTTP_CLIENT_TIMEOUT +else: + try: + AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = int(AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER) + except Exception: + AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = AIOHTTP_CLIENT_TIMEOUT + + +#################################### +# AIOHTTP Connection Pool +#################################### + +AIOHTTP_POOL_CONNECTIONS = os.environ.get('AIOHTTP_POOL_CONNECTIONS', '') +if AIOHTTP_POOL_CONNECTIONS == '': + AIOHTTP_POOL_CONNECTIONS = None +else: + try: + AIOHTTP_POOL_CONNECTIONS = int(AIOHTTP_POOL_CONNECTIONS) + except ValueError: + AIOHTTP_POOL_CONNECTIONS = None + +AIOHTTP_POOL_CONNECTIONS_PER_HOST = os.environ.get('AIOHTTP_POOL_CONNECTIONS_PER_HOST', '') +if AIOHTTP_POOL_CONNECTIONS_PER_HOST == '': + AIOHTTP_POOL_CONNECTIONS_PER_HOST = None +else: + try: + AIOHTTP_POOL_CONNECTIONS_PER_HOST = int(AIOHTTP_POOL_CONNECTIONS_PER_HOST) + except ValueError: + AIOHTTP_POOL_CONNECTIONS_PER_HOST = None + +AIOHTTP_POOL_DNS_TTL = os.environ.get('AIOHTTP_POOL_DNS_TTL', '300') +try: + AIOHTTP_POOL_DNS_TTL = int(AIOHTTP_POOL_DNS_TTL) + if AIOHTTP_POOL_DNS_TTL < 0: + AIOHTTP_POOL_DNS_TTL = 300 +except ValueError: + AIOHTTP_POOL_DNS_TTL = 300 + +RAG_EMBEDDING_TIMEOUT = os.environ.get('RAG_EMBEDDING_TIMEOUT', '') + +if RAG_EMBEDDING_TIMEOUT == '': + RAG_EMBEDDING_TIMEOUT = None +else: + try: + RAG_EMBEDDING_TIMEOUT = int(RAG_EMBEDDING_TIMEOUT) + except Exception: + RAG_EMBEDDING_TIMEOUT = None + + +#################################### +# Auth #################################### WEBUI_AUTH = os.environ.get('WEBUI_AUTH', 'True').lower() == 'true' @@ -531,6 +597,37 @@ WEBUI_AUTH = os.environ.get('WEBUI_AUTH', 'True').lower() == 'true' ENABLE_INITIAL_ADMIN_SIGNUP = os.environ.get('ENABLE_INITIAL_ADMIN_SIGNUP', 'False').lower() == 'true' ENABLE_SIGNUP_PASSWORD_CONFIRMATION = os.environ.get('ENABLE_SIGNUP_PASSWORD_CONFIRMATION', 'False').lower() == 'true' +#################################### +# Secret key & cookies +#################################### + +# WEBUI_JWT_SECRET_KEY is deprecated; use WEBUI_SECRET_KEY instead. +WEBUI_SECRET_KEY = os.environ.get( + 'WEBUI_SECRET_KEY', + os.environ.get('WEBUI_JWT_SECRET_KEY', 't0p-s3cr3t'), +) + +WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get( + 'WEBUI_SESSION_COOKIE_SAME_SITE', 'lax' +) +WEBUI_SESSION_COOKIE_SECURE = ( + os.environ.get('WEBUI_SESSION_COOKIE_SECURE', 'false').lower() == 'true' +) +WEBUI_AUTH_COOKIE_SAME_SITE = os.environ.get( + 'WEBUI_AUTH_COOKIE_SAME_SITE', WEBUI_SESSION_COOKIE_SAME_SITE +) +WEBUI_AUTH_COOKIE_SECURE = os.environ.get( + 'WEBUI_AUTH_COOKIE_SECURE', + os.environ.get('WEBUI_SESSION_COOKIE_SECURE', 'false'), +).lower() == 'true' + +if WEBUI_AUTH and WEBUI_SECRET_KEY == '': + raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND) + +ENABLE_COMPRESSION_MIDDLEWARE = ( + os.environ.get('ENABLE_COMPRESSION_MIDDLEWARE', 'True').lower() == 'true' +) + #################################### # Admin Account Runtime Creation #################################### @@ -584,34 +681,6 @@ ENABLE_OPENAI_API_PASSTHROUGH = os.environ.get('ENABLE_OPENAI_API_PASSTHROUGH', WEBUI_AUTH_SIGNOUT_REDIRECT_URL = os.environ.get('WEBUI_AUTH_SIGNOUT_REDIRECT_URL', None) -#################################### -# WEBUI_SECRET_KEY -#################################### - -WEBUI_SECRET_KEY = os.environ.get( - 'WEBUI_SECRET_KEY', - os.environ.get('WEBUI_JWT_SECRET_KEY', 't0p-s3cr3t'), # DEPRECATED: remove at next major version -) - -WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get('WEBUI_SESSION_COOKIE_SAME_SITE', 'lax') - -WEBUI_SESSION_COOKIE_SECURE = os.environ.get('WEBUI_SESSION_COOKIE_SECURE', 'false').lower() == 'true' - -WEBUI_AUTH_COOKIE_SAME_SITE = os.environ.get('WEBUI_AUTH_COOKIE_SAME_SITE', WEBUI_SESSION_COOKIE_SAME_SITE) - -WEBUI_AUTH_COOKIE_SECURE = ( - os.environ.get( - 'WEBUI_AUTH_COOKIE_SECURE', - os.environ.get('WEBUI_SESSION_COOKIE_SECURE', 'false'), - ).lower() - == 'true' -) - -if WEBUI_AUTH and WEBUI_SECRET_KEY == '': - raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND) - -ENABLE_COMPRESSION_MIDDLEWARE = os.environ.get('ENABLE_COMPRESSION_MIDDLEWARE', 'True').lower() == 'true' - #################################### # OAUTH Configuration #################################### @@ -677,6 +746,68 @@ if LICENSE_PUBLIC_KEY: ) +#################################### +# WEBUI Identity +#################################### + +WEBUI_NAME = os.environ.get('WEBUI_NAME', 'Open WebUI') +if WEBUI_NAME != 'Open WebUI': + WEBUI_NAME += ' (Open WebUI)' + +WEBUI_FAVICON_URL = 'https://openwebui.com/favicon.png' +WEBUI_BUILD_HASH = os.environ.get('WEBUI_BUILD_HASH', 'dev-build') +TRUSTED_SIGNATURE_KEY = os.environ.get('TRUSTED_SIGNATURE_KEY', '') + +#################################### +# Feature flags +#################################### + +SAFE_MODE = os.environ.get('SAFE_MODE', 'False').lower() == 'true' +ENABLE_EASTER_EGGS = os.environ.get('ENABLE_EASTER_EGGS', 'True').lower() == 'true' +ENABLE_STAR_SESSIONS_MIDDLEWARE = os.environ.get( + 'ENABLE_STAR_SESSIONS_MIDDLEWARE', 'False' +).lower() == 'true' + +ENABLE_PROFILE_IMAGE_URL_FORWARDING = os.environ.get('ENABLE_PROFILE_IMAGE_URL_FORWARDING', 'True').lower() == 'true' +PROFILE_IMAGE_ALLOWED_MIME_TYPES = frozenset( + t.strip() + for t in os.environ.get( + 'PROFILE_IMAGE_ALLOWED_MIME_TYPES', + 'image/png,image/jpeg,image/gif,image/webp', + ).split(',') + if t.strip() +) + +#################################### +# Forward Headers +#################################### + +ENABLE_FORWARD_USER_INFO_HEADERS = os.environ.get('ENABLE_FORWARD_USER_INFO_HEADERS', 'False').lower() == 'true' + +FORWARD_USER_INFO_HEADER_USER_NAME = os.environ.get('FORWARD_USER_INFO_HEADER_USER_NAME', 'X-OpenWebUI-User-Name') +FORWARD_USER_INFO_HEADER_USER_ID = os.environ.get('FORWARD_USER_INFO_HEADER_USER_ID', 'X-OpenWebUI-User-Id') +FORWARD_USER_INFO_HEADER_USER_EMAIL = os.environ.get('FORWARD_USER_INFO_HEADER_USER_EMAIL', 'X-OpenWebUI-User-Email') +FORWARD_USER_INFO_HEADER_USER_ROLE = os.environ.get('FORWARD_USER_INFO_HEADER_USER_ROLE', 'X-OpenWebUI-User-Role') +FORWARD_SESSION_INFO_HEADER_MESSAGE_ID = os.environ.get( + 'FORWARD_SESSION_INFO_HEADER_MESSAGE_ID', 'X-OpenWebUI-Message-Id' +) +FORWARD_SESSION_INFO_HEADER_CHAT_ID = os.environ.get('FORWARD_SESSION_INFO_HEADER_CHAT_ID', 'X-OpenWebUI-Chat-Id') + +#################################### +# Progressive Web App +#################################### + +EXTERNAL_PWA_MANIFEST_URL = os.environ.get('EXTERNAL_PWA_MANIFEST_URL', None) + +#################################### +# GROUP DEFAULTS +#################################### + +# Controls the default "Who can share to this group" setting for new groups. +# Env var values: "true" (anyone), "false" (no one), "members" (only group members). +_default_group_share = os.environ.get('DEFAULT_GROUP_SHARE_PERMISSION', 'members').strip().lower() +DEFAULT_GROUP_SHARE_PERMISSION = 'members' if _default_group_share == 'members' else _default_group_share == 'true' + #################################### # MODELS #################################### @@ -750,177 +881,6 @@ else: CHAT_STREAM_RESPONSE_CHUNK_MAX_BUFFER_SIZE = None -#################################### -# WEBSOCKET SUPPORT -#################################### - -ENABLE_WEBSOCKET_SUPPORT = os.environ.get('ENABLE_WEBSOCKET_SUPPORT', 'True').lower() == 'true' - - -WEBSOCKET_MANAGER = os.environ.get('WEBSOCKET_MANAGER', '') - -WEBSOCKET_REDIS_OPTIONS = os.environ.get('WEBSOCKET_REDIS_OPTIONS', '') - - -if WEBSOCKET_REDIS_OPTIONS == '': - if REDIS_SOCKET_CONNECT_TIMEOUT: - WEBSOCKET_REDIS_OPTIONS = {'socket_connect_timeout': REDIS_SOCKET_CONNECT_TIMEOUT} - else: - log.debug('No WEBSOCKET_REDIS_OPTIONS provided, defaulting to None') - WEBSOCKET_REDIS_OPTIONS = None -else: - try: - WEBSOCKET_REDIS_OPTIONS = json.loads(WEBSOCKET_REDIS_OPTIONS) - except Exception: - log.warning('Invalid WEBSOCKET_REDIS_OPTIONS, defaulting to None') - WEBSOCKET_REDIS_OPTIONS = None - -WEBSOCKET_REDIS_URL = os.environ.get('WEBSOCKET_REDIS_URL', REDIS_URL) -WEBSOCKET_REDIS_CLUSTER = os.environ.get('WEBSOCKET_REDIS_CLUSTER', str(REDIS_CLUSTER)).lower() == 'true' - -websocket_redis_lock_timeout = os.environ.get('WEBSOCKET_REDIS_LOCK_TIMEOUT', '60') - -try: - WEBSOCKET_REDIS_LOCK_TIMEOUT = int(websocket_redis_lock_timeout) -except ValueError: - WEBSOCKET_REDIS_LOCK_TIMEOUT = 60 - -WEBSOCKET_SENTINEL_HOSTS = os.environ.get('WEBSOCKET_SENTINEL_HOSTS', '') -WEBSOCKET_SENTINEL_PORT = os.environ.get('WEBSOCKET_SENTINEL_PORT', '26379') -WEBSOCKET_SERVER_LOGGING = os.environ.get('WEBSOCKET_SERVER_LOGGING', 'False').lower() == 'true' -WEBSOCKET_SERVER_ENGINEIO_LOGGING = ( - os.environ.get( - 'WEBSOCKET_SERVER_ENGINEIO_LOGGING', - os.environ.get('WEBSOCKET_SERVER_LOGGING', 'False'), - ).lower() - == 'true' -) -WEBSOCKET_SERVER_PING_TIMEOUT = os.environ.get('WEBSOCKET_SERVER_PING_TIMEOUT', '20') -try: - WEBSOCKET_SERVER_PING_TIMEOUT = int(WEBSOCKET_SERVER_PING_TIMEOUT) -except ValueError: - WEBSOCKET_SERVER_PING_TIMEOUT = 20 - -WEBSOCKET_SERVER_PING_INTERVAL = os.environ.get('WEBSOCKET_SERVER_PING_INTERVAL', '25') -try: - WEBSOCKET_SERVER_PING_INTERVAL = int(WEBSOCKET_SERVER_PING_INTERVAL) -except ValueError: - WEBSOCKET_SERVER_PING_INTERVAL = 25 - -WEBSOCKET_EVENT_CALLER_TIMEOUT = os.environ.get('WEBSOCKET_EVENT_CALLER_TIMEOUT', '') - -if WEBSOCKET_EVENT_CALLER_TIMEOUT == '': - WEBSOCKET_EVENT_CALLER_TIMEOUT = None -else: - try: - WEBSOCKET_EVENT_CALLER_TIMEOUT = int(WEBSOCKET_EVENT_CALLER_TIMEOUT) - except ValueError: - WEBSOCKET_EVENT_CALLER_TIMEOUT = 300 - - -REQUESTS_VERIFY = os.environ.get('REQUESTS_VERIFY', 'True').lower() == 'true' - -AIOHTTP_CLIENT_TIMEOUT = os.environ.get('AIOHTTP_CLIENT_TIMEOUT', '') - -if AIOHTTP_CLIENT_TIMEOUT == '': - AIOHTTP_CLIENT_TIMEOUT = None -else: - try: - AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT) - except Exception: - AIOHTTP_CLIENT_TIMEOUT = 300 - - -AIOHTTP_CLIENT_SESSION_SSL = os.environ.get('AIOHTTP_CLIENT_SESSION_SSL', 'True').lower() == 'true' - -# When False (default), outbound HTTP requests do not follow 3xx redirects. -# This prevents redirect-based SSRF where a public URL 302-redirects to an -# internal address (RFC 1918, loopback, cloud-metadata 169.254.169.254). -# Set to True only if your deployment requires redirect following and you -# have other SSRF protections in place (e.g. egress firewall). -AIOHTTP_CLIENT_ALLOW_REDIRECTS = os.environ.get('AIOHTTP_CLIENT_ALLOW_REDIRECTS', 'False').lower() == 'true' - -AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = os.environ.get( - 'AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST', - os.environ.get('AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST', '10'), -) - -if AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST == '': - AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = None -else: - try: - AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = int(AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST) - except Exception: - AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = 10 - - -AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = os.environ.get('AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA', '10') - -if AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA == '': - AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = None -else: - try: - AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = int(AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA) - except Exception: - AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = 10 - - -AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = ( - os.environ.get('AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL', 'True').lower() == 'true' -) - -AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = os.environ.get('AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER', '') - -if AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER == '': - AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = AIOHTTP_CLIENT_TIMEOUT -else: - try: - AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = int(AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER) - except Exception: - AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = AIOHTTP_CLIENT_TIMEOUT - - -#################################### -# AIOHTTP Connection Pool -#################################### - -AIOHTTP_POOL_CONNECTIONS = os.environ.get('AIOHTTP_POOL_CONNECTIONS', '') -if AIOHTTP_POOL_CONNECTIONS == '': - AIOHTTP_POOL_CONNECTIONS = None -else: - try: - AIOHTTP_POOL_CONNECTIONS = int(AIOHTTP_POOL_CONNECTIONS) - except ValueError: - AIOHTTP_POOL_CONNECTIONS = None - -AIOHTTP_POOL_CONNECTIONS_PER_HOST = os.environ.get('AIOHTTP_POOL_CONNECTIONS_PER_HOST', '') -if AIOHTTP_POOL_CONNECTIONS_PER_HOST == '': - AIOHTTP_POOL_CONNECTIONS_PER_HOST = None -else: - try: - AIOHTTP_POOL_CONNECTIONS_PER_HOST = int(AIOHTTP_POOL_CONNECTIONS_PER_HOST) - except ValueError: - AIOHTTP_POOL_CONNECTIONS_PER_HOST = None - -AIOHTTP_POOL_DNS_TTL = os.environ.get('AIOHTTP_POOL_DNS_TTL', '300') -try: - AIOHTTP_POOL_DNS_TTL = int(AIOHTTP_POOL_DNS_TTL) - if AIOHTTP_POOL_DNS_TTL < 0: - AIOHTTP_POOL_DNS_TTL = 300 -except ValueError: - AIOHTTP_POOL_DNS_TTL = 300 - -RAG_EMBEDDING_TIMEOUT = os.environ.get('RAG_EMBEDDING_TIMEOUT', '') - -if RAG_EMBEDDING_TIMEOUT == '': - RAG_EMBEDDING_TIMEOUT = None -else: - try: - RAG_EMBEDDING_TIMEOUT = int(RAG_EMBEDDING_TIMEOUT) - except Exception: - RAG_EMBEDDING_TIMEOUT = None - - #################################### # SENTENCE TRANSFORMERS #################################### @@ -964,6 +924,18 @@ SENTENCE_TRANSFORMERS_CROSS_ENCODER_SIGMOID_ACTIVATION_FUNCTION = ( os.environ.get('SENTENCE_TRANSFORMERS_CROSS_ENCODER_SIGMOID_ACTIVATION_FUNCTION', 'True').lower() == 'true' ) +#################################### +# TOOLS/FUNCTIONS PIP OPTIONS +#################################### + +ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS = ( + os.environ.get('ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS', 'True').lower() == 'true' +) + +PIP_OPTIONS = os.getenv('PIP_OPTIONS', '').split() +PIP_PACKAGE_INDEX_OPTIONS = os.getenv('PIP_PACKAGE_INDEX_OPTIONS', '').split() + + #################################### # OFFLINE_MODE #################################### @@ -976,7 +948,7 @@ if OFFLINE_MODE: ENABLE_VERSION_UPDATE_CHECK = False #################################### -# AUDIT LOGGING +# Audit logging #################################### @@ -1043,10 +1015,18 @@ OTEL_BASIC_AUTH_USERNAME = os.environ.get('OTEL_BASIC_AUTH_USERNAME', '') OTEL_BASIC_AUTH_PASSWORD = os.environ.get('OTEL_BASIC_AUTH_PASSWORD', '') OTEL_METRICS_EXPORT_INTERVAL_MILLIS = int(os.environ.get('OTEL_METRICS_EXPORT_INTERVAL_MILLIS', '10000')) -OTEL_METRICS_BASIC_AUTH_USERNAME = os.environ.get('OTEL_METRICS_BASIC_AUTH_USERNAME', OTEL_BASIC_AUTH_USERNAME) -OTEL_METRICS_BASIC_AUTH_PASSWORD = os.environ.get('OTEL_METRICS_BASIC_AUTH_PASSWORD', OTEL_BASIC_AUTH_PASSWORD) -OTEL_LOGS_BASIC_AUTH_USERNAME = os.environ.get('OTEL_LOGS_BASIC_AUTH_USERNAME', OTEL_BASIC_AUTH_USERNAME) -OTEL_LOGS_BASIC_AUTH_PASSWORD = os.environ.get('OTEL_LOGS_BASIC_AUTH_PASSWORD', OTEL_BASIC_AUTH_PASSWORD) +OTEL_METRICS_BASIC_AUTH_USERNAME = os.environ.get( + 'OTEL_METRICS_BASIC_AUTH_USERNAME', OTEL_BASIC_AUTH_USERNAME +) +OTEL_METRICS_BASIC_AUTH_PASSWORD = os.environ.get( + 'OTEL_METRICS_BASIC_AUTH_PASSWORD', OTEL_BASIC_AUTH_PASSWORD +) +OTEL_LOGS_BASIC_AUTH_USERNAME = os.environ.get( + 'OTEL_LOGS_BASIC_AUTH_USERNAME', OTEL_BASIC_AUTH_USERNAME +) +OTEL_LOGS_BASIC_AUTH_PASSWORD = os.environ.get( + 'OTEL_LOGS_BASIC_AUTH_PASSWORD', OTEL_BASIC_AUTH_PASSWORD +) OTEL_OTLP_SPAN_EXPORTER = os.environ.get('OTEL_OTLP_SPAN_EXPORTER', 'grpc').lower() # grpc or http @@ -1057,30 +1037,3 @@ OTEL_METRICS_OTLP_SPAN_EXPORTER = os.environ.get( OTEL_LOGS_OTLP_SPAN_EXPORTER = os.environ.get( 'OTEL_LOGS_OTLP_SPAN_EXPORTER', OTEL_OTLP_SPAN_EXPORTER ).lower() # grpc or http - -#################################### -# TOOLS/FUNCTIONS PIP OPTIONS -#################################### - -ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS = ( - os.environ.get('ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS', 'True').lower() == 'true' -) - -PIP_OPTIONS = os.getenv('PIP_OPTIONS', '').split() -PIP_PACKAGE_INDEX_OPTIONS = os.getenv('PIP_PACKAGE_INDEX_OPTIONS', '').split() - - -#################################### -# PROGRESSIVE WEB APP OPTIONS -#################################### - -EXTERNAL_PWA_MANIFEST_URL = os.environ.get('EXTERNAL_PWA_MANIFEST_URL') - -#################################### -# GROUP DEFAULTS -#################################### - -# Controls the default "Who can share to this group" setting for new groups. -# Env var values: "true" (anyone), "false" (no one), "members" (only group members). -_default_group_share = os.environ.get('DEFAULT_GROUP_SHARE_PERMISSION', 'members').strip().lower() -DEFAULT_GROUP_SHARE_PERMISSION = 'members' if _default_group_share == 'members' else _default_group_share == 'true'