Skip to content
This repository was archived by the owner on Jan 13, 2026. It is now read-only.

Commit 93590d2

Browse files
authored
Merge branch 'dev' into patch-1
2 parents 0888ddd + 8218906 commit 93590d2

File tree

173 files changed

+2558
-1988
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

173 files changed

+2558
-1988
lines changed

backend/open_webui/config.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import logging
33
import os
44
import shutil
5+
import base64
6+
57
from datetime import datetime
68
from pathlib import Path
79
from typing import Generic, Optional, TypeVar
@@ -586,15 +588,27 @@ def oidc_oauth_register(client):
586588

587589
STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static")).resolve()
588590

591+
592+
def override_static(path: str, content: str):
593+
# Ensure path is safe
594+
if "/" in path or ".." in path:
595+
log.error(f"Invalid path: {path}")
596+
return
597+
598+
file_path = os.path.join(STATIC_DIR, path)
599+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
600+
601+
with open(file_path, "wb") as f:
602+
f.write(base64.b64decode(content)) # Convert Base64 back to raw binary
603+
604+
589605
frontend_favicon = FRONTEND_BUILD_DIR / "static" / "favicon.png"
590606

591607
if frontend_favicon.exists():
592608
try:
593609
shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png")
594610
except Exception as e:
595611
logging.error(f"An error occurred: {e}")
596-
else:
597-
logging.warning(f"Frontend favicon not found at {frontend_favicon}")
598612

599613
frontend_splash = FRONTEND_BUILD_DIR / "static" / "splash.png"
600614

@@ -603,12 +617,18 @@ def oidc_oauth_register(client):
603617
shutil.copyfile(frontend_splash, STATIC_DIR / "splash.png")
604618
except Exception as e:
605619
logging.error(f"An error occurred: {e}")
606-
else:
607-
logging.warning(f"Frontend splash not found at {frontend_splash}")
620+
621+
frontend_loader = FRONTEND_BUILD_DIR / "static" / "loader.js"
622+
623+
if frontend_loader.exists():
624+
try:
625+
shutil.copyfile(frontend_loader, STATIC_DIR / "loader.js")
626+
except Exception as e:
627+
logging.error(f"An error occurred: {e}")
608628

609629

610630
####################################
611-
# CUSTOM_NAME
631+
# CUSTOM_NAME (Legacy)
612632
####################################
613633

614634
CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "")
@@ -650,6 +670,16 @@ def oidc_oauth_register(client):
650670
pass
651671

652672

673+
####################################
674+
# LICENSE_KEY
675+
####################################
676+
677+
LICENSE_KEY = PersistentConfig(
678+
"LICENSE_KEY",
679+
"license.key",
680+
os.environ.get("LICENSE_KEY", ""),
681+
)
682+
653683
####################################
654684
# STORAGE PROVIDER
655685
####################################
@@ -1853,6 +1883,11 @@ class BannerModel(BaseModel):
18531883
int(os.getenv("RAG_WEB_SEARCH_CONCURRENT_REQUESTS", "10")),
18541884
)
18551885

1886+
RAG_WEB_SEARCH_TRUST_ENV = PersistentConfig(
1887+
"RAG_WEB_SEARCH_TRUST_ENV",
1888+
"rag.web.search.trust_env",
1889+
os.getenv("RAG_WEB_SEARCH_TRUST_ENV", False),
1890+
)
18561891

18571892
####################################
18581893
# Images

backend/open_webui/env.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113

114114
WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
115115

116+
TRUSTED_SIGNATURE_KEY = os.environ.get("TRUSTED_SIGNATURE_KEY", "")
116117

117118
####################################
118119
# ENV (dev,test,prod)

backend/open_webui/main.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
from open_webui.models.users import UserModel, Users
8989

9090
from open_webui.config import (
91+
LICENSE_KEY,
9192
# Ollama
9293
ENABLE_OLLAMA_API,
9394
OLLAMA_BASE_URLS,
@@ -175,6 +176,7 @@
175176
RAG_WEB_SEARCH_ENGINE,
176177
RAG_WEB_SEARCH_RESULT_COUNT,
177178
RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
179+
RAG_WEB_SEARCH_TRUST_ENV,
178180
RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
179181
JINA_API_KEY,
180182
SEARCHAPI_API_KEY,
@@ -313,15 +315,17 @@
313315
from open_webui.utils.access_control import has_access
314316

315317
from open_webui.utils.auth import (
318+
get_license_data,
316319
decode_token,
317320
get_admin_user,
318321
get_verified_user,
319322
)
320-
from open_webui.utils.oauth import oauth_manager
323+
from open_webui.utils.oauth import OAuthManager
321324
from open_webui.utils.security_headers import SecurityHeadersMiddleware
322325

323326
from open_webui.tasks import stop_task, list_tasks # Import from tasks.py
324327

328+
325329
if SAFE_MODE:
326330
print("SAFE MODE ENABLED")
327331
Functions.deactivate_all_functions()
@@ -348,12 +352,12 @@ async def get_response(self, path: str, scope):
348352

349353
print(
350354
rf"""
351-
___ __ __ _ _ _ ___
352-
/ _ \ _ __ ___ _ __ \ \ / /__| |__ | | | |_ _|
353-
| | | | '_ \ / _ \ '_ \ \ \ /\ / / _ \ '_ \| | | || |
354-
| |_| | |_) | __/ | | | \ V V / __/ |_) | |_| || |
355-
\___/| .__/ \___|_| |_| \_/\_/ \___|_.__/ \___/|___|
356-
|_|
355+
██████╗ ██████╗ ███████╗███╗ ██╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗██╗
356+
██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██║ ██║██╔════╝██╔══██╗██║ ██║██║
357+
██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ █╗ ██║█████╗ ██████╔╝██║ ██║██║
358+
██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ██║███╗██║██╔══╝ ██╔══██╗██║ ██║██║
359+
╚██████╔╝██║ ███████╗██║ ╚████║ ╚███╔███╔╝███████╗██████╔╝╚██████╔╝██║
360+
╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═════╝ ╚═╝
357361
358362
359363
v{VERSION} - building the best open-source AI user interface.
@@ -368,6 +372,9 @@ async def lifespan(app: FastAPI):
368372
if RESET_CONFIG_ON_START:
369373
reset_config()
370374

375+
if app.state.config.LICENSE_KEY:
376+
get_license_data(app, app.state.config.LICENSE_KEY)
377+
371378
asyncio.create_task(periodic_usage_pool_cleanup())
372379
yield
373380

@@ -379,8 +386,12 @@ async def lifespan(app: FastAPI):
379386
lifespan=lifespan,
380387
)
381388

389+
oauth_manager = OAuthManager(app)
390+
382391
app.state.config = AppConfig()
383392

393+
app.state.WEBUI_NAME = WEBUI_NAME
394+
app.state.config.LICENSE_KEY = LICENSE_KEY
384395

385396
########################################
386397
#
@@ -482,10 +493,10 @@ async def lifespan(app: FastAPI):
482493
app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
483494
app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
484495

496+
app.state.USER_COUNT = None
485497
app.state.TOOLS = {}
486498
app.state.FUNCTIONS = {}
487499

488-
489500
########################################
490501
#
491502
# RETRIEVAL
@@ -558,6 +569,7 @@ async def lifespan(app: FastAPI):
558569

559570
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
560571
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
572+
app.state.config.RAG_WEB_SEARCH_TRUST_ENV = RAG_WEB_SEARCH_TRUST_ENV
561573

562574
app.state.EMBEDDING_FUNCTION = None
563575
app.state.ef = None
@@ -1069,7 +1081,7 @@ async def get_app_config(request: Request):
10691081
return {
10701082
**({"onboarding": True} if onboarding else {}),
10711083
"status": True,
1072-
"name": WEBUI_NAME,
1084+
"name": app.state.WEBUI_NAME,
10731085
"version": VERSION,
10741086
"default_locale": str(DEFAULT_LOCALE),
10751087
"oauth": {
@@ -1204,7 +1216,7 @@ async def get_app_changelog():
12041216

12051217
@app.get("/oauth/{provider}/login")
12061218
async def oauth_login(provider: str, request: Request):
1207-
return await oauth_manager.handle_login(provider, request)
1219+
return await oauth_manager.handle_login(request, provider)
12081220

12091221

12101222
# OAuth login logic is as follows:
@@ -1215,14 +1227,14 @@ async def oauth_login(provider: str, request: Request):
12151227
# - Email addresses are considered unique, so we fail registration if the email address is already taken
12161228
@app.get("/oauth/{provider}/callback")
12171229
async def oauth_callback(provider: str, request: Request, response: Response):
1218-
return await oauth_manager.handle_callback(provider, request, response)
1230+
return await oauth_manager.handle_callback(request, provider, response)
12191231

12201232

12211233
@app.get("/manifest.json")
12221234
async def get_manifest_json():
12231235
return {
1224-
"name": WEBUI_NAME,
1225-
"short_name": WEBUI_NAME,
1236+
"name": app.state.WEBUI_NAME,
1237+
"short_name": app.state.WEBUI_NAME,
12261238
"description": "Open WebUI is an open, extensible, user-friendly interface for AI that adapts to your workflow.",
12271239
"start_url": "/",
12281240
"display": "standalone",
@@ -1249,8 +1261,8 @@ async def get_manifest_json():
12491261
async def get_opensearch_xml():
12501262
xml_content = rf"""
12511263
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
1252-
<ShortName>{WEBUI_NAME}</ShortName>
1253-
<Description>Search {WEBUI_NAME}</Description>
1264+
<ShortName>{app.state.WEBUI_NAME}</ShortName>
1265+
<Description>Search {app.state.WEBUI_NAME}</Description>
12541266
<InputEncoding>UTF-8</InputEncoding>
12551267
<Image width="16" height="16" type="image/x-icon">{app.state.config.WEBUI_URL}/static/favicon.png</Image>
12561268
<Url type="text/html" method="get" template="{app.state.config.WEBUI_URL}/?q={"{searchTerms}"}"/>

backend/open_webui/retrieval/web/duckduckgo.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,15 @@ def search_duckduckgo(
3232
# Convert the search results into a list
3333
search_results = [r for r in ddgs_gen]
3434

35-
# Create an empty list to store the SearchResult objects
36-
results = []
37-
# Iterate over each search result
38-
for result in search_results:
39-
# Create a SearchResult object and append it to the results list
40-
results.append(
41-
SearchResult(
42-
link=result["href"],
43-
title=result.get("title"),
44-
snippet=result.get("body"),
45-
)
46-
)
4735
if filter_list:
48-
results = get_filtered_results(results, filter_list)
36+
search_results = get_filtered_results(search_results, filter_list)
37+
4938
# Return the list of search results
50-
return results
39+
return [
40+
SearchResult(
41+
link=result["href"],
42+
title=result.get("title"),
43+
snippet=result.get("body"),
44+
)
45+
for result in search_results
46+
]

backend/open_webui/retrieval/web/tavily.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from typing import Optional
23

34
import requests
45
from open_webui.retrieval.web.main import SearchResult
@@ -8,7 +9,13 @@
89
log.setLevel(SRC_LOG_LEVELS["RAG"])
910

1011

11-
def search_tavily(api_key: str, query: str, count: int) -> list[SearchResult]:
12+
def search_tavily(
13+
api_key: str,
14+
query: str,
15+
count: int,
16+
filter_list: Optional[list[str]] = None,
17+
# **kwargs,
18+
) -> list[SearchResult]:
1219
"""Search using Tavily's Search API and return the results as a list of SearchResult objects.
1320
1421
Args:
@@ -20,8 +27,8 @@ def search_tavily(api_key: str, query: str, count: int) -> list[SearchResult]:
2027
"""
2128
url = "https://api.tavily.com/search"
2229
data = {"query": query, "api_key": api_key}
23-
24-
response = requests.post(url, json=data)
30+
include_domain = filter_list
31+
response = requests.post(url, include_domain, json=data)
2532
response.raise_for_status()
2633

2734
json_response = response.json()

0 commit comments

Comments
 (0)