Skip to content

Commit b5f4c85

Browse files
authored
Merge pull request open-webui#15014 from open-webui/dev
0.6.15
2 parents 6325613 + 340d982 commit b5f4c85

File tree

153 files changed

+8212
-3017
lines changed

Some content is hidden

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

153 files changed

+8212
-3017
lines changed

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.6.15] - 2025-06-16
9+
10+
### Added
11+
12+
- 🖼️ **Global Image Compression Option**: Effortlessly set image compression globally so all image uploads and outputs are optimized, speeding up load times and saving bandwidth—perfect for teams dealing with large files or limited network resources.
13+
- 🎤 **Custom Speech-to-Text Content-Type for Transcription**: Define custom content types for audio transcription, ensuring compatibility with diverse audio sources and unlocking smoother, more accurate transcriptions in advanced setups.
14+
- 🗂️ **LDAP Group Synchronization (Experimental)**: Automatically sync user groups from your LDAP directory directly into Open WebUI for seamless enterprise access management—simplifies identity integration and governance across your organization.
15+
- 📈 **OpenTelemetry Metrics via OTLP Exporter (Experimental)**: Gain enterprise-grade analytics and monitor your AI usage in real time with experimental OpenTelemetry Metrics support—connect to any OTLP-compatible backend for instant insights into performance, load, and user interactions.
16+
- 🕰️ **See User Message Timestamps on Hover (Chat Bubble UI)**: Effortlessly check when any user message was sent by hovering over it in Chat Bubble mode—no more switching screens or digging through logs for context.
17+
- 🗂️ **Leaderboard Sorting Options**: Sort the leaderboard directly in the UI for a clearer, more actionable view of top performers, models, or tools—making analysis and recognition quick and easy for teams.
18+
- 🏆 **Evaluation Details Modal in Feedbacks and Leaderboard**: Dive deeper with new modals that display detailed evaluation information when reviewing feedbacks and leaderboard rankings—accelerates learning, progress tracking, and quality improvement.
19+
- 🔄 **Support for Multiple Pages in External Document Loaders**: Effortlessly extract and work with content spanning multiple pages in external documents, giving you complete flexibility for in-depth research and document workflows.
20+
- 🌐 **New Accessibility Enhancements Across the Interface**: Benefit from significant accessibility improvements—tab navigation, ARIA roles/labels, better high-contrast text/modes, accessible modals, and more—making Open WebUI more usable and equitable for everyone, including those using assistive technologies.
21+
-**Performance & Stability Upgrades Across Frontend and Backend**: Enjoy a smoother, more reliable experience with numerous behind-the-scenes optimizations and refactoring on both frontend and backend—resulting in faster load times, fewer errors, and even greater stability throughout your workflows.
22+
- 🌏 **Updated and Expanded Localizations**: Enjoy improved, up-to-date translations for Finnish, German (now with model pinning features), Korean, Russian, Simplified Chinese, Spanish, and more—making every interaction smoother, clearer, and more intuitive for international users.
23+
24+
### Fixed
25+
26+
- 🦾 **Ollama Error Messages More Descriptive**: Receive clearer, more actionable error messages when something goes wrong with Ollama models—making troubleshooting and user support faster and more effective.
27+
- 🌐 **Bypass Webloader Now Works as Expected**: Resolved an issue where the "bypass webloader" feature failed to function correctly, ensuring web search bypasses operate smoothly and reliably for lighter, faster query results.
28+
- 🔍 **Prevent Redundant Documents in Citation List**: The expanded citation list no longer shows duplicate documents, offering a cleaner, easier-to-digest reference experience when reviewing sources in knowledge and research workflows.
29+
- 🛡️ **Trusted Header Email Matching is Now Case-Insensitive**: Fixed a critical authentication issue where email case sensitivity could cause secure headers to mismatch, ensuring robust, seamless login and session management in all environments.
30+
- ⚙️ **Direct Tool Server Input Accepts Empty Strings**: You can now submit direct tool server commands without unexpected errors when passing empty-string values, improving integration and automation efficiency.
31+
- 📄 **Citation Page Number for Page 1 is Now Displayed**: Corrected an oversight where references for page 1 documents were missing the page number; citations are now always accurate and fully visible.
32+
- 📒 **Notes Access Restored**: Fixed an issue where some users could not access their notes—everyone can now view and manage their notes reliably, ensuring seamless documentation and workflow continuity.
33+
- 🛑 **OAuth Callback Double-Slash Issue Resolved**: Fixed rare cases where an extra slash in OAuth callbacks caused failed logins or redirects, making third-party login integrations more reliable.
34+
35+
### Changed
36+
37+
- 🔑 **Dedicated Permission for System Prompts**: System prompt access is now controlled by its own specific permission instead of being grouped with general chat controls, empowering admins with finer-grained management over who can view or modify system prompts for enhanced security and workflow customization.
38+
- 🛠️ **YouTube Transcript API and python-pptx Updated**: Enjoy better performance, reliability, and broader compatibility thanks to underlying library upgrades—less friction with media-rich and presentation workflows.
39+
40+
### Removed
41+
42+
- 🗑️ **Console Logging Disabled in Production**: All 'console.log' and 'console.debug' statements are now disabled in production, guaranteeing improved security and cleaner browser logs for end users by removing extraneous technical output.
43+
844
## [0.6.14] - 2025-06-10
945

1046
### Added

backend/open_webui/config.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,10 @@ def oidc_oauth_register(client):
10771077
os.environ.get("USER_PERMISSIONS_CHAT_CONTROLS", "True").lower() == "true"
10781078
)
10791079

1080+
USER_PERMISSIONS_CHAT_SYSTEM_PROMPT = (
1081+
os.environ.get("USER_PERMISSIONS_CHAT_SYSTEM_PROMPT", "True").lower() == "true"
1082+
)
1083+
10801084
USER_PERMISSIONS_CHAT_FILE_UPLOAD = (
10811085
os.environ.get("USER_PERMISSIONS_CHAT_FILE_UPLOAD", "True").lower() == "true"
10821086
)
@@ -1162,6 +1166,7 @@ def oidc_oauth_register(client):
11621166
},
11631167
"chat": {
11641168
"controls": USER_PERMISSIONS_CHAT_CONTROLS,
1169+
"system_prompt": USER_PERMISSIONS_CHAT_SYSTEM_PROMPT,
11651170
"file_upload": USER_PERMISSIONS_CHAT_FILE_UPLOAD,
11661171
"delete": USER_PERMISSIONS_CHAT_DELETE,
11671172
"edit": USER_PERMISSIONS_CHAT_EDIT,
@@ -2102,6 +2107,27 @@ class BannerModel(BaseModel):
21022107
),
21032108
)
21042109

2110+
FILE_IMAGE_COMPRESSION_WIDTH = PersistentConfig(
2111+
"FILE_IMAGE_COMPRESSION_WIDTH",
2112+
"file.image_compression_width",
2113+
(
2114+
int(os.environ.get("FILE_IMAGE_COMPRESSION_WIDTH"))
2115+
if os.environ.get("FILE_IMAGE_COMPRESSION_WIDTH")
2116+
else None
2117+
),
2118+
)
2119+
2120+
FILE_IMAGE_COMPRESSION_HEIGHT = PersistentConfig(
2121+
"FILE_IMAGE_COMPRESSION_HEIGHT",
2122+
"file.image_compression_height",
2123+
(
2124+
int(os.environ.get("FILE_IMAGE_COMPRESSION_HEIGHT"))
2125+
if os.environ.get("FILE_IMAGE_COMPRESSION_HEIGHT")
2126+
else None
2127+
),
2128+
)
2129+
2130+
21052131
RAG_ALLOWED_FILE_EXTENSIONS = PersistentConfig(
21062132
"RAG_ALLOWED_FILE_EXTENSIONS",
21072133
"rag.file.allowed_extensions",
@@ -2901,6 +2927,18 @@ class BannerModel(BaseModel):
29012927
os.getenv("AUDIO_STT_MODEL", ""),
29022928
)
29032929

2930+
AUDIO_STT_SUPPORTED_CONTENT_TYPES = PersistentConfig(
2931+
"AUDIO_STT_SUPPORTED_CONTENT_TYPES",
2932+
"audio.stt.supported_content_types",
2933+
[
2934+
content_type.strip()
2935+
for content_type in os.environ.get(
2936+
"AUDIO_STT_SUPPORTED_CONTENT_TYPES", ""
2937+
).split(",")
2938+
if content_type.strip()
2939+
],
2940+
)
2941+
29042942
AUDIO_STT_AZURE_API_KEY = PersistentConfig(
29052943
"AUDIO_STT_AZURE_API_KEY",
29062944
"audio.stt.azure.api_key",
@@ -3075,3 +3113,22 @@ class BannerModel(BaseModel):
30753113
LDAP_CIPHERS = PersistentConfig(
30763114
"LDAP_CIPHERS", "ldap.server.ciphers", os.environ.get("LDAP_CIPHERS", "ALL")
30773115
)
3116+
3117+
# For LDAP Group Management
3118+
ENABLE_LDAP_GROUP_MANAGEMENT = PersistentConfig(
3119+
"ENABLE_LDAP_GROUP_MANAGEMENT",
3120+
"ldap.group.enable_management",
3121+
os.environ.get("ENABLE_LDAP_GROUP_MANAGEMENT", "False").lower() == "true",
3122+
)
3123+
3124+
ENABLE_LDAP_GROUP_CREATION = PersistentConfig(
3125+
"ENABLE_LDAP_GROUP_CREATION",
3126+
"ldap.group.enable_creation",
3127+
os.environ.get("ENABLE_LDAP_GROUP_CREATION", "False").lower() == "true",
3128+
)
3129+
3130+
LDAP_ATTRIBUTE_FOR_GROUPS = PersistentConfig(
3131+
"LDAP_ATTRIBUTE_FOR_GROUPS",
3132+
"ldap.server.attribute_for_groups",
3133+
os.environ.get("LDAP_ATTRIBUTE_FOR_GROUPS", "memberOf"),
3134+
)

backend/open_webui/env.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ def parse_section(section):
539539
####################################
540540

541541
ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true"
542+
ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == "true"
542543
OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get(
543544
"OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"
544545
)

backend/open_webui/main.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
from open_webui.socket.main import (
5858
app as socket_app,
5959
periodic_usage_pool_cleanup,
60+
get_models_in_use,
61+
get_active_user_ids,
6062
)
6163
from open_webui.routers import (
6264
audio,
@@ -157,6 +159,7 @@
157159
# Audio
158160
AUDIO_STT_ENGINE,
159161
AUDIO_STT_MODEL,
162+
AUDIO_STT_SUPPORTED_CONTENT_TYPES,
160163
AUDIO_STT_OPENAI_API_BASE_URL,
161164
AUDIO_STT_OPENAI_API_KEY,
162165
AUDIO_STT_AZURE_API_KEY,
@@ -208,6 +211,8 @@
208211
RAG_ALLOWED_FILE_EXTENSIONS,
209212
RAG_FILE_MAX_COUNT,
210213
RAG_FILE_MAX_SIZE,
214+
FILE_IMAGE_COMPRESSION_WIDTH,
215+
FILE_IMAGE_COMPRESSION_HEIGHT,
211216
RAG_OPENAI_API_BASE_URL,
212217
RAG_OPENAI_API_KEY,
213218
RAG_AZURE_OPENAI_BASE_URL,
@@ -349,6 +354,10 @@
349354
LDAP_CA_CERT_FILE,
350355
LDAP_VALIDATE_CERT,
351356
LDAP_CIPHERS,
357+
# LDAP Group Management
358+
ENABLE_LDAP_GROUP_MANAGEMENT,
359+
ENABLE_LDAP_GROUP_CREATION,
360+
LDAP_ATTRIBUTE_FOR_GROUPS,
352361
# Misc
353362
ENV,
354363
CACHE_DIR,
@@ -676,6 +685,11 @@ async def lifespan(app: FastAPI):
676685
app.state.config.LDAP_VALIDATE_CERT = LDAP_VALIDATE_CERT
677686
app.state.config.LDAP_CIPHERS = LDAP_CIPHERS
678687

688+
# For LDAP Group Management
689+
app.state.config.ENABLE_LDAP_GROUP_MANAGEMENT = ENABLE_LDAP_GROUP_MANAGEMENT
690+
app.state.config.ENABLE_LDAP_GROUP_CREATION = ENABLE_LDAP_GROUP_CREATION
691+
app.state.config.LDAP_ATTRIBUTE_FOR_GROUPS = LDAP_ATTRIBUTE_FOR_GROUPS
692+
679693

680694
app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
681695
app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
@@ -701,9 +715,13 @@ async def lifespan(app: FastAPI):
701715
app.state.config.TOP_K_RERANKER = RAG_TOP_K_RERANKER
702716
app.state.config.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD
703717
app.state.config.HYBRID_BM25_WEIGHT = RAG_HYBRID_BM25_WEIGHT
718+
719+
704720
app.state.config.ALLOWED_FILE_EXTENSIONS = RAG_ALLOWED_FILE_EXTENSIONS
705721
app.state.config.FILE_MAX_SIZE = RAG_FILE_MAX_SIZE
706722
app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT
723+
app.state.config.FILE_IMAGE_COMPRESSION_WIDTH = FILE_IMAGE_COMPRESSION_WIDTH
724+
app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT = FILE_IMAGE_COMPRESSION_HEIGHT
707725

708726

709727
app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT
@@ -948,10 +966,12 @@ async def lifespan(app: FastAPI):
948966
#
949967
########################################
950968

951-
app.state.config.STT_OPENAI_API_BASE_URL = AUDIO_STT_OPENAI_API_BASE_URL
952-
app.state.config.STT_OPENAI_API_KEY = AUDIO_STT_OPENAI_API_KEY
953969
app.state.config.STT_ENGINE = AUDIO_STT_ENGINE
954970
app.state.config.STT_MODEL = AUDIO_STT_MODEL
971+
app.state.config.STT_SUPPORTED_CONTENT_TYPES = AUDIO_STT_SUPPORTED_CONTENT_TYPES
972+
973+
app.state.config.STT_OPENAI_API_BASE_URL = AUDIO_STT_OPENAI_API_BASE_URL
974+
app.state.config.STT_OPENAI_API_KEY = AUDIO_STT_OPENAI_API_KEY
955975

956976
app.state.config.WHISPER_MODEL = WHISPER_MODEL
957977
app.state.config.WHISPER_VAD_FILTER = WHISPER_VAD_FILTER
@@ -1362,6 +1382,17 @@ async def chat_completion(
13621382
request, response, form_data, user, metadata, model, events, tasks
13631383
)
13641384
except Exception as e:
1385+
log.debug(f"Error in chat completion: {e}")
1386+
if metadata.get("chat_id") and metadata.get("message_id"):
1387+
# Update the chat message with the error
1388+
Chats.upsert_message_to_chat_by_id_and_message_id(
1389+
metadata["chat_id"],
1390+
metadata["message_id"],
1391+
{
1392+
"error": {"content": str(e)},
1393+
},
1394+
)
1395+
13651396
raise HTTPException(
13661397
status_code=status.HTTP_400_BAD_REQUEST,
13671398
detail=str(e),
@@ -1533,6 +1564,10 @@ async def get_app_config(request: Request):
15331564
"file": {
15341565
"max_size": app.state.config.FILE_MAX_SIZE,
15351566
"max_count": app.state.config.FILE_MAX_COUNT,
1567+
"image_compression": {
1568+
"width": app.state.config.FILE_IMAGE_COMPRESSION_WIDTH,
1569+
"height": app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT,
1570+
},
15361571
},
15371572
"permissions": {**app.state.config.USER_PERMISSIONS},
15381573
"google_drive": {
@@ -1618,6 +1653,19 @@ async def get_app_changelog():
16181653
return {key: CHANGELOG[key] for idx, key in enumerate(CHANGELOG) if idx < 5}
16191654

16201655

1656+
@app.get("/api/usage")
1657+
async def get_current_usage(user=Depends(get_verified_user)):
1658+
"""
1659+
Get current usage statistics for Open WebUI.
1660+
This is an experimental endpoint and subject to change.
1661+
"""
1662+
try:
1663+
return {"model_ids": get_models_in_use(), "user_ids": get_active_user_ids()}
1664+
except Exception as e:
1665+
log.error(f"Error getting usage statistics: {e}")
1666+
raise HTTPException(status_code=500, detail="Internal Server Error")
1667+
1668+
16211669
############################
16221670
# OAuth Login & Callback
16231671
############################

backend/open_webui/models/groups.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,39 @@ def remove_user_from_all_groups(self, user_id: str) -> bool:
207207
except Exception:
208208
return False
209209

210-
def sync_user_groups_by_group_names(
210+
def create_groups_by_group_names(
211211
self, user_id: str, group_names: list[str]
212-
) -> bool:
212+
) -> list[GroupModel]:
213+
214+
# check for existing groups
215+
existing_groups = self.get_groups()
216+
existing_group_names = {group.name for group in existing_groups}
217+
218+
new_groups = []
219+
220+
with get_db() as db:
221+
for group_name in group_names:
222+
if group_name not in existing_group_names:
223+
new_group = GroupModel(
224+
id=str(uuid.uuid4()),
225+
user_id=user_id,
226+
name=group_name,
227+
description="",
228+
created_at=int(time.time()),
229+
updated_at=int(time.time()),
230+
)
231+
try:
232+
result = Group(**new_group.model_dump())
233+
db.add(result)
234+
db.commit()
235+
db.refresh(result)
236+
new_groups.append(GroupModel.model_validate(result))
237+
except Exception as e:
238+
log.exception(e)
239+
continue
240+
return new_groups
241+
242+
def sync_groups_by_group_names(self, user_id: str, group_names: list[str]) -> bool:
213243
with get_db() as db:
214244
try:
215245
groups = db.query(Group).filter(Group.name.in_(group_names)).all()

backend/open_webui/retrieval/loaders/external_document.py

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import requests
2-
import logging
2+
import logging, os
33
from typing import Iterator, List, Union
44

55
from langchain_core.document_loaders import BaseLoader
@@ -25,7 +25,7 @@ def __init__(
2525
self.file_path = file_path
2626
self.mime_type = mime_type
2727

28-
def load(self) -> list[Document]:
28+
def load(self) -> List[Document]:
2929
with open(self.file_path, "rb") as f:
3030
data = f.read()
3131

@@ -36,23 +36,48 @@ def load(self) -> list[Document]:
3636
if self.api_key is not None:
3737
headers["Authorization"] = f"Bearer {self.api_key}"
3838

39+
try:
40+
headers["X-Filename"] = os.path.basename(self.file_path)
41+
except:
42+
pass
43+
3944
url = self.url
4045
if url.endswith("/"):
4146
url = url[:-1]
4247

43-
r = requests.put(f"{url}/process", data=data, headers=headers)
48+
try:
49+
response = requests.put(f"{url}/process", data=data, headers=headers)
50+
except Exception as e:
51+
log.error(f"Error connecting to endpoint: {e}")
52+
raise Exception(f"Error connecting to endpoint: {e}")
53+
54+
if response.ok:
4455

45-
if r.ok:
46-
res = r.json()
56+
response_data = response.json()
57+
if response_data:
58+
if isinstance(response_data, dict):
59+
return [
60+
Document(
61+
page_content=response_data.get("page_content"),
62+
metadata=response_data.get("metadata"),
63+
)
64+
]
65+
elif isinstance(response_data, list):
66+
documents = []
67+
for document in response_data:
68+
documents.append(
69+
Document(
70+
page_content=document.get("page_content"),
71+
metadata=document.get("metadata"),
72+
)
73+
)
74+
return documents
75+
else:
76+
raise Exception("Error loading document: Unable to parse content")
4777

48-
if res:
49-
return [
50-
Document(
51-
page_content=res.get("page_content"),
52-
metadata=res.get("metadata"),
53-
)
54-
]
5578
else:
5679
raise Exception("Error loading document: No content returned")
5780
else:
58-
raise Exception(f"Error loading document: {r.status_code} {r.text}")
81+
raise Exception(
82+
f"Error loading document: {response.status_code} {response.text}"
83+
)

0 commit comments

Comments
 (0)
close