diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index 1a1f0d1f4f..5be1ac21b3 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -13,6 +13,8 @@ body:
- **Before submitting a bug report**: Please check the [Issues](https://github.com/open-webui/open-webui/issues) and [Discussions](https://github.com/open-webui/open-webui/discussions) sections to see if a similar issue has already been reported. If unsure, start a discussion first, as this helps us efficiently focus on improving the project. Duplicates may be closed without notice. **Please search for existing issues and discussions.**
+ - Check for opened, **but also for (recently) CLOSED issues** as the issue you are trying to report **might already have been fixed!**
+
- **Respectful collaboration**: Open WebUI is a volunteer-driven project with a single maintainer and contributors who also have full-time jobs. Please be constructive and respectful in your communication.
- **Contributing**: If you encounter an issue, consider submitting a pull request or forking the project. We prioritize preventing contributor burnout to maintain Open WebUI's quality.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index fa82ae26a1..0ec871f328 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -4,14 +4,15 @@
**Before submitting, make sure you've checked the following:**
-- [ ] **Target branch:** Please verify that the pull request targets the `dev` branch.
+- [ ] **Target branch:** Verify that the pull request targets the `dev` branch. Not targeting the `dev` branch may lead to immediate closure of the PR.
- [ ] **Description:** Provide a concise description of the changes made in this pull request.
- [ ] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description.
-- [ ] **Documentation:** Have you updated relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs), or other documentation sources?
+- [ ] **Documentation:** If necessary, update relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs) like environment variables, the tutorials, or other documentation sources.
- [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
-- [ ] **Testing:** Have you written and run sufficient tests to validate the changes?
+- [ ] **Testing:** Perform manual tests to verify the implemented fix/feature works as intended AND does not break any other functionality. Take this as an opportunity to make screenshots of the feature/fix and include it in the PR description.
+- [ ] **Agentic AI Code:**: Confirm this Pull Request is **not written by any AI Agent** or has at least gone through additional human review **and** manual testing. If any AI Agent is the co-author of this PR, it may lead to immediate closure of the PR.
- [ ] **Code review:** Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards?
-- [ ] **Prefix:** To clearly categorize this pull request, prefix the pull request title using one of the following:
+- [ ] **Title Prefix:** To clearly categorize this pull request, prefix the pull request title using one of the following:
- **BREAKING CHANGE**: Significant changes that may affect compatibility
- **build**: Changes that affect the build system or external dependencies
- **ci**: Changes to our continuous integration processes or workflows
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a69bb9dace..2e4938dfe1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,75 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.6.33] - 2025-10-08
+
+### Added
+
+- 🎨 Workspace interface received a comprehensive redesign across Models, Knowledge, Prompts, and Tools sections, featuring reorganized controls, view filters for created vs shared items, tag selectors, improved visual hierarchy, and streamlined import/export functionality. [Commit](https://github.com/open-webui/open-webui/commit/2c59a288603d8c5f004f223ee00fef37cc763a8e), [Commit](https://github.com/open-webui/open-webui/commit/6050c86ab6ef6b8c96dd3f99c62a6867011b67a4), [Commit](https://github.com/open-webui/open-webui/commit/96ecb47bc71c072aa34ef2be10781b042bef4e8c), [Commit](https://github.com/open-webui/open-webui/commit/2250d102b28075a9611696e911536547abb8b38a), [Commit](https://github.com/open-webui/open-webui/commit/23c8f6d507bfee75ab0015a3e2972d5c26f7e9bf), [Commit](https://github.com/open-webui/open-webui/commit/a743b16728c6ae24b8befbc2d7f24eb9e20c4ad5)
+- 🛠️ Functions admin interface received a comprehensive redesign with creator attribution display, ownership filters for created vs shared items, improved organization, and refined styling. [Commit](https://github.com/open-webui/open-webui/commit/f5e1a42f51acc0b9d5b63a33c1ca2e42470239c1)
+- ⚡ Page initialization performance is significantly improved through parallel data loading and optimized folder API calls, reducing initial page load time. [#17559](https://github.com/open-webui/open-webui/pull/17559), [#17889](https://github.com/open-webui/open-webui/pull/17889)
+- ⚡ Chat overview component is now dynamically loaded on demand, reducing initial page bundle size by approximately 470KB and improving first-screen loading speed. [#17595](https://github.com/open-webui/open-webui/pull/17595)
+- 📁 Folders can now be attached to chats using the "#" command, automatically expanding to include all files within the folder for streamlined knowledge base integration. [Commit](https://github.com/open-webui/open-webui/commit/d2cb78179d66dc85188172a08622d4c97a2ea1ee)
+- 📱 Progressive Web App now supports Android share target functionality, allowing users to share web pages, YouTube videos, and text directly to Open WebUI from the system share menu. [#17633](https://github.com/open-webui/open-webui/pull/17633), [#17125](https://github.com/open-webui/open-webui/issues/17125)
+- 🗄️ Redis session storage is now available as an experimental option for OAuth authentication flows via the ENABLE_STAR_SESSIONS_MIDDLEWARE environment variable, providing shared session state across multi-replica deployments to address CSRF errors, though currently only basic Redis setups are supported. [#17223](https://github.com/open-webui/open-webui/pull/17223), [#15373](https://github.com/open-webui/open-webui/issues/15373), [Docs:Commit](https://github.com/open-webui/docs/commit/14052347f165d1b597615370373d7289ce44c7f9)
+- 📊 Vega and Vega-Lite chart visualization renderers are now supported in code blocks, enabling inline rendering of data visualizations with automatic compilation of Vega-Lite specifications. [#18033](https://github.com/open-webui/open-webui/pull/18033), [#18040](https://github.com/open-webui/open-webui/pull/18040), [#18022](https://github.com/open-webui/open-webui/issues/18022)
+- 🔗 OpenAI connections now support custom HTTP headers, enabling users to configure authentication and routing headers for specific deployment requirements. [#18021](https://github.com/open-webui/open-webui/pull/18021), [#9732](https://github.com/open-webui/open-webui/discussions/9732)
+- 🔐 OpenID Connect authentication now supports OIDC providers without email scope via the ENABLE_OAUTH_WITHOUT_EMAIL environment variable, enabling compatibility with identity providers that don't expose email addresses. [#18047](https://github.com/open-webui/open-webui/pull/18047), [#18045](https://github.com/open-webui/open-webui/issues/18045)
+- 🤖 Ollama model management modal now features individual model update cancellation, comprehensive tooltips for all buttons, and streamlined notification behavior to reduce toast spam. [#16863](https://github.com/open-webui/open-webui/pull/16863)
+- ☁️ OneDrive file picker now includes search functionality and "My Organization" pivot for business accounts, enabling easier file discovery across organizational content. [#17930](https://github.com/open-webui/open-webui/pull/17930), [#17929](https://github.com/open-webui/open-webui/issues/17929)
+- 📊 Chat overview flow diagram now supports toggling between vertical and horizontal layout orientations for improved visualization flexibility. [#17941](https://github.com/open-webui/open-webui/pull/17941)
+- 🔊 OpenAI Text-to-Speech engine now supports additional parameters, allowing users to customize TTS behavior with provider-specific options via JSON configuration. [#17985](https://github.com/open-webui/open-webui/issues/17985), [#17188](https://github.com/open-webui/open-webui/pull/17188)
+- 🛠️ Tool server list now displays server name, URL, and type (OpenAPI or MCP) for easier identification and management. [#18062](https://github.com/open-webui/open-webui/issues/18062)
+- 📁 Folders now remember the last selected model, automatically applying it when starting new chats within that folder. [#17836](https://github.com/open-webui/open-webui/issues/17836)
+- 🔢 Ollama embedding endpoint now supports the optional dimensions parameter for controlling embedding output size, compatible with Ollama v0.11.11 and later. [#17942](https://github.com/open-webui/open-webui/pull/17942)
+- ⚡ Workspace knowledge page load time is improved by removing redundant API calls, enhancing overall responsiveness. [#18057](https://github.com/open-webui/open-webui/pull/18057)
+- ⚡ File metadata query performance is enhanced by selecting only relevant columns instead of retrieving entire records, reducing database overhead. [#18013](https://github.com/open-webui/open-webui/pull/18013)
+- 📄 Note PDF exports now include titles and properly render in dark mode with appropriate background colors. [Commit](https://github.com/open-webui/open-webui/commit/216fb5c3db1a223ffe6e72d97aa9551fe0e2d028)
+- 📄 Docling document extraction now supports additional parameters for VLM pipeline configuration, enabling customized vision model settings. [#17363](https://github.com/open-webui/open-webui/pull/17363)
+- ⚙️ Server startup script now supports passing arbitrary arguments to uvicorn, enabling custom server configuration options. [#17919](https://github.com/open-webui/open-webui/pull/17919), [#17918](https://github.com/open-webui/open-webui/issues/17918)
+- 🔄 Various improvements were implemented across the frontend and backend to enhance performance, stability, and security.
+- 🌐 Translations for German, Danish, Spanish, Korean, Portuguese (Brazil), Simplified Chinese, and Traditional Chinese were enhanced and expanded.
+
+### Fixed
+
+- 💬 System prompts are no longer duplicated in chat requests, eliminating confusion and excessive token usage caused by repeated instructions being sent to models. [#17198](https://github.com/open-webui/open-webui/issues/17198), [#16855](https://github.com/open-webui/open-webui/issues/16855)
+- 🔐 MCP OAuth 2.1 authentication now complies with the standard by implementing PKCE with S256 code challenge method and explicitly passing client credentials during token authorization, resolving "code_challenge: Field required" and "client_id: Field required" errors when connecting to OAuth-secured MCP servers. [Commit](https://github.com/open-webui/open-webui/commit/911a114ad459f5deebd97543c13c2b90196efb54), [#18010](https://github.com/open-webui/open-webui/issues/18010), [#18087](https://github.com/open-webui/open-webui/pull/18087)
+- 🔐 OAuth signup flow now handles password hashing correctly by migrating from passlib to native bcrypt, preventing failures when passwords exceed 72 bytes. [#17917](https://github.com/open-webui/open-webui/issues/17917)
+- 🔐 OAuth token refresh errors are resolved by properly registering and storing OAuth clients, fixing "Constructor parameter should be str" exceptions for Google, Microsoft, and OIDC providers. [#17829](https://github.com/open-webui/open-webui/issues/17829)
+- 🔐 OAuth server metadata URL is now correctly accessed via the proper attribute, fixing automatic token refresh and logout functionality for Microsoft OAuth provider when OPENID_PROVIDER_URL is not set. [#18065](https://github.com/open-webui/open-webui/pull/18065)
+- 🔐 OAuth credential decryption failures now allow the application to start gracefully with clear error messages instead of crashing, preventing complete service outages when WEBUI_SECRET_KEY mismatches occur during database migrations or environment changes. [#18094](https://github.com/open-webui/open-webui/pull/18094), [#18092](https://github.com/open-webui/open-webui/issues/18092)
+- 🔐 OAuth 2.1 server discovery now correctly attempts all configured discovery URLs in sequence instead of only trying the first URL. [#17906](https://github.com/open-webui/open-webui/pull/17906), [#17904](https://github.com/open-webui/open-webui/issues/17904), [#18026](https://github.com/open-webui/open-webui/pull/18026)
+- 🔐 Login redirect now correctly honors the redirect query parameter after authentication, ensuring users are returned to their intended destination with query parameters intact instead of defaulting to the homepage. [#18071](https://github.com/open-webui/open-webui/issues/18071)
+- ☁️ OneDrive Business integration authentication regression is resolved, ensuring the popup now properly triggers when connecting to OneDrive accounts. [#17902](https://github.com/open-webui/open-webui/pull/17902), [#17825](https://github.com/open-webui/open-webui/discussions/17825), [#17816](https://github.com/open-webui/open-webui/issues/17816)
+- 👥 Default group settings now persist correctly after page navigation, ensuring configuration changes are properly saved and retained. [#17899](https://github.com/open-webui/open-webui/issues/17899), [#18003](https://github.com/open-webui/open-webui/issues/18003)
+- 📁 Folder data integrity is now verified on retrieval, automatically fixing orphaned folders with invalid parent references and ensuring proper cascading deletion of nested folder structures. [Commit](https://github.com/open-webui/open-webui/commit/5448618dd5ea181b9635b77040cef60926a902ff)
+- 🗄️ Redis Sentinel and Redis Cluster configurations with the experimental ENABLE_STAR_SESSIONS_MIDDLEWARE feature are now properly isolated by making the feature opt-in only, preventing ReadOnlyError failures when connecting to read replicas in multi-node Redis deployments. [#18073](https://github.com/open-webui/open-webui/issues/18073)
+- 📊 Mermaid and Vega diagram rendering now displays error toast notifications when syntax errors are detected, helping users identify and fix diagram issues instead of silently failing. [#18068](https://github.com/open-webui/open-webui/pull/18068)
+- 🤖 Reasoning models that return reasoning_content instead of content no longer cause NoneType errors during chat title generation, follow-up suggestions, and tag generation. [#18080](https://github.com/open-webui/open-webui/pull/18080)
+- 📚 Citation rendering now correctly handles multiple source references in a single bracket, parsing formats like [1,2] and [1, 2] into separate clickable citation links. [#18120](https://github.com/open-webui/open-webui/pull/18120)
+- 🔍 Web search now handles individual source failures gracefully, continuing to process remaining sources instead of failing entirely when a single URL is unreachable or returns an error. [Commit](https://github.com/open-webui/open-webui/commit/e000494e488090c5f66989a2b3f89d3eaeb7946b), [Commit](https://github.com/open-webui/open-webui/commit/53e98620bff38ab9280aee5165af0a704bdd99b9)
+- 🔍 Hybrid search with reranking now handles empty result sets gracefully instead of crashing with ValueError when all results are filtered out due to relevance thresholds. [#18096](https://github.com/open-webui/open-webui/issues/18096)
+- 🔍 Reranking models without defined padding tokens now work correctly by automatically falling back to eos_token_id as pad_token_id, fixing "Cannot handle batch sizes > 1" errors for models like Qwen3-Reranker. [#18108](https://github.com/open-webui/open-webui/pull/18108), [#16027](https://github.com/open-webui/open-webui/discussions/16027)
+- 🔍 Model selector search now correctly returns results for non-admin users by dynamically updating the search index when the model list changes, fixing a race condition that caused empty search results. [#17996](https://github.com/open-webui/open-webui/pull/17996), [#17960](https://github.com/open-webui/open-webui/pull/17960)
+- ⚡ Task model function calling performance is improved by excluding base64 image data from payloads, significantly reducing token count and memory usage when images are present in conversations. [#17897](https://github.com/open-webui/open-webui/pull/17897)
+- 🤖 Text selection "Ask" action now correctly recognizes and uses local models configured via direct connections instead of only showing external provider models. [#17896](https://github.com/open-webui/open-webui/issues/17896)
+- 🛑 Task cancellation API now returns accurate response status, correctly reporting successful cancellations instead of incorrectly indicating failures. [#17920](https://github.com/open-webui/open-webui/issues/17920)
+- 💬 Follow-up query suggestions are now generated and displayed in temporary chats, matching the behavior of saved chats. [#14987](https://github.com/open-webui/open-webui/issues/14987)
+- 🔊 Azure Text-to-Speech now properly escapes special characters like ampersands in SSML, preventing HTTP 400 errors and ensuring audio generation succeeds for all text content. [#17962](https://github.com/open-webui/open-webui/issues/17962)
+- 🛠️ OpenAPI tool server calls with optional parameters now execute successfully even when no arguments are provided, removing the incorrect requirement for a request body. [#18036](https://github.com/open-webui/open-webui/issues/18036)
+- 🛠️ MCP mode tool server connections no longer incorrectly validate the OpenAPI path field, allowing seamless switching between OpenAPI and MCP connection types. [#17989](https://github.com/open-webui/open-webui/pull/17989), [#17988](https://github.com/open-webui/open-webui/issues/17988)
+- 🛠️ Third-party tool responses containing non-UTF8 or invalid byte sequences are now handled gracefully without causing request failures. [#17882](https://github.com/open-webui/open-webui/pull/17882)
+- 🎨 Workspace filter dropdown now correctly renders model tags as strings instead of displaying individual characters, fixing broken filtering interface when models have multiple tags. [#18034](https://github.com/open-webui/open-webui/issues/18034)
+- ⌨️ Ctrl+Enter keyboard shortcut now correctly sends messages in mobile and narrow browser views on Chrome instead of inserting newlines. [#17975](https://github.com/open-webui/open-webui/issues/17975)
+- ⌨️ Tab characters are now preserved when pasting code or formatted text into the chat input box in plain text mode. [#17958](https://github.com/open-webui/open-webui/issues/17958)
+- 📋 Text selection copying from the chat input box now correctly copies only the selected text instead of the entire textbox content. [#17911](https://github.com/open-webui/open-webui/issues/17911)
+- 🔍 Web search query logging now uses debug level instead of info level, preventing user search queries from appearing in production logs. [#17888](https://github.com/open-webui/open-webui/pull/17888)
+- 📝 Debug print statements in middleware were removed to prevent excessive log pollution and respect configured logging levels. [#17943](https://github.com/open-webui/open-webui/issues/17943)
+
+### Changed
+
+- 🗄️ Milvus vector database dependency is updated from pymilvus 2.5.0 to 2.6.2, ensuring compatibility with newer Milvus versions but requiring users on older Milvus instances to either upgrade their database or manually downgrade the pymilvus package. [#18066](https://github.com/open-webui/open-webui/pull/18066)
+
## [0.6.32] - 2025-09-29
### Added
diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
index 3a4309438a..bd73807621 100644
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -605,8 +605,8 @@ def load_oauth_providers():
OAUTH_PROVIDERS.clear()
if GOOGLE_CLIENT_ID.value and GOOGLE_CLIENT_SECRET.value:
- def google_oauth_register(client: OAuth):
- client.register(
+ def google_oauth_register(oauth: OAuth):
+ client = oauth.register(
name="google",
client_id=GOOGLE_CLIENT_ID.value,
client_secret=GOOGLE_CLIENT_SECRET.value,
@@ -621,6 +621,7 @@ def load_oauth_providers():
},
redirect_uri=GOOGLE_REDIRECT_URI.value,
)
+ return client
OAUTH_PROVIDERS["google"] = {
"redirect_uri": GOOGLE_REDIRECT_URI.value,
@@ -633,8 +634,8 @@ def load_oauth_providers():
and MICROSOFT_CLIENT_TENANT_ID.value
):
- def microsoft_oauth_register(client: OAuth):
- client.register(
+ def microsoft_oauth_register(oauth: OAuth):
+ client = oauth.register(
name="microsoft",
client_id=MICROSOFT_CLIENT_ID.value,
client_secret=MICROSOFT_CLIENT_SECRET.value,
@@ -649,6 +650,7 @@ def load_oauth_providers():
},
redirect_uri=MICROSOFT_REDIRECT_URI.value,
)
+ return client
OAUTH_PROVIDERS["microsoft"] = {
"redirect_uri": MICROSOFT_REDIRECT_URI.value,
@@ -658,8 +660,8 @@ def load_oauth_providers():
if GITHUB_CLIENT_ID.value and GITHUB_CLIENT_SECRET.value:
- def github_oauth_register(client: OAuth):
- client.register(
+ def github_oauth_register(oauth: OAuth):
+ client = oauth.register(
name="github",
client_id=GITHUB_CLIENT_ID.value,
client_secret=GITHUB_CLIENT_SECRET.value,
@@ -677,6 +679,7 @@ def load_oauth_providers():
},
redirect_uri=GITHUB_CLIENT_REDIRECT_URI.value,
)
+ return client
OAUTH_PROVIDERS["github"] = {
"redirect_uri": GITHUB_CLIENT_REDIRECT_URI.value,
@@ -690,7 +693,7 @@ def load_oauth_providers():
and OPENID_PROVIDER_URL.value
):
- def oidc_oauth_register(client: OAuth):
+ def oidc_oauth_register(oauth: OAuth):
client_kwargs = {
"scope": OAUTH_SCOPES.value,
**(
@@ -716,7 +719,7 @@ def load_oauth_providers():
% ("S256", OAUTH_CODE_CHALLENGE_METHOD.value)
)
- client.register(
+ client = oauth.register(
name="oidc",
client_id=OAUTH_CLIENT_ID.value,
client_secret=OAUTH_CLIENT_SECRET.value,
@@ -724,6 +727,7 @@ def load_oauth_providers():
client_kwargs=client_kwargs,
redirect_uri=OPENID_REDIRECT_URI.value,
)
+ return client
OAUTH_PROVIDERS["oidc"] = {
"name": OAUTH_PROVIDER_NAME.value,
@@ -733,8 +737,8 @@ def load_oauth_providers():
if FEISHU_CLIENT_ID.value and FEISHU_CLIENT_SECRET.value:
- def feishu_oauth_register(client: OAuth):
- client.register(
+ def feishu_oauth_register(oauth: OAuth):
+ client = oauth.register(
name="feishu",
client_id=FEISHU_CLIENT_ID.value,
client_secret=FEISHU_CLIENT_SECRET.value,
@@ -752,6 +756,7 @@ def load_oauth_providers():
},
redirect_uri=FEISHU_REDIRECT_URI.value,
)
+ return client
OAUTH_PROVIDERS["feishu"] = {
"register": feishu_oauth_register,
@@ -2310,6 +2315,18 @@ DOCLING_SERVER_URL = PersistentConfig(
os.getenv("DOCLING_SERVER_URL", "http://docling:5001"),
)
+docling_params = os.getenv("DOCLING_PARAMS", "")
+try:
+ docling_params = json.loads(docling_params)
+except json.JSONDecodeError:
+ docling_params = {}
+
+DOCLING_PARAMS = PersistentConfig(
+ "DOCLING_PARAMS",
+ "rag.docling_params",
+ docling_params,
+)
+
DOCLING_DO_OCR = PersistentConfig(
"DOCLING_DO_OCR",
"rag.docling_do_ocr",
@@ -3361,6 +3378,19 @@ AUDIO_TTS_OPENAI_API_KEY = PersistentConfig(
os.getenv("AUDIO_TTS_OPENAI_API_KEY", OPENAI_API_KEY),
)
+audio_tts_openai_params = os.getenv("AUDIO_TTS_OPENAI_PARAMS", "")
+try:
+ audio_tts_openai_params = json.loads(audio_tts_openai_params)
+except json.JSONDecodeError:
+ audio_tts_openai_params = {}
+
+AUDIO_TTS_OPENAI_PARAMS = PersistentConfig(
+ "AUDIO_TTS_OPENAI_PARAMS",
+ "audio.tts.openai.params",
+ audio_tts_openai_params,
+)
+
+
AUDIO_TTS_API_KEY = PersistentConfig(
"AUDIO_TTS_API_KEY",
"audio.tts.api_key",
diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py
index e02424f969..8f9c1fbc44 100644
--- a/backend/open_webui/env.py
+++ b/backend/open_webui/env.py
@@ -212,6 +212,11 @@ ENABLE_FORWARD_USER_INFO_HEADERS = (
os.environ.get("ENABLE_FORWARD_USER_INFO_HEADERS", "False").lower() == "true"
)
+# Experimental feature, may be removed in future
+ENABLE_STAR_SESSIONS_MIDDLEWARE = (
+ os.environ.get("ENABLE_STAR_SESSIONS_MIDDLEWARE", "False").lower() == "true"
+)
+
####################################
# WEBUI_BUILD_HASH
####################################
@@ -468,7 +473,9 @@ ENABLE_COMPRESSION_MIDDLEWARE = (
####################################
# OAUTH Configuration
####################################
-
+ENABLE_OAUTH_EMAIL_FALLBACK = (
+ os.environ.get("ENABLE_OAUTH_EMAIL_FALLBACK", "False").lower() == "true"
+)
ENABLE_OAUTH_ID_TOKEN_COOKIE = (
os.environ.get("ENABLE_OAUTH_ID_TOKEN_COOKIE", "True").lower() == "true"
@@ -482,7 +489,6 @@ OAUTH_SESSION_TOKEN_ENCRYPTION_KEY = os.environ.get(
"OAUTH_SESSION_TOKEN_ENCRYPTION_KEY", WEBUI_SECRET_KEY
)
-
####################################
# SCIM Configuration
####################################
diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py
index 904399af14..221c20f305 100644
--- a/backend/open_webui/main.py
+++ b/backend/open_webui/main.py
@@ -8,6 +8,7 @@ import shutil
import sys
import time
import random
+import re
from uuid import uuid4
@@ -174,13 +175,14 @@ from open_webui.config import (
AUDIO_STT_AZURE_LOCALES,
AUDIO_STT_AZURE_BASE_URL,
AUDIO_STT_AZURE_MAX_SPEAKERS,
- AUDIO_TTS_API_KEY,
AUDIO_TTS_ENGINE,
AUDIO_TTS_MODEL,
+ AUDIO_TTS_VOICE,
AUDIO_TTS_OPENAI_API_BASE_URL,
AUDIO_TTS_OPENAI_API_KEY,
+ AUDIO_TTS_OPENAI_PARAMS,
+ AUDIO_TTS_API_KEY,
AUDIO_TTS_SPLIT_ON,
- AUDIO_TTS_VOICE,
AUDIO_TTS_AZURE_SPEECH_REGION,
AUDIO_TTS_AZURE_SPEECH_BASE_URL,
AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
@@ -246,6 +248,7 @@ from open_webui.config import (
EXTERNAL_DOCUMENT_LOADER_API_KEY,
TIKA_SERVER_URL,
DOCLING_SERVER_URL,
+ DOCLING_PARAMS,
DOCLING_DO_OCR,
DOCLING_FORCE_OCR,
DOCLING_OCR_ENGINE,
@@ -447,6 +450,7 @@ from open_webui.env import (
ENABLE_OTEL,
EXTERNAL_PWA_MANIFEST_URL,
AIOHTTP_CLIENT_SESSION_SSL,
+ ENABLE_STAR_SESSIONS_MIDDLEWARE,
)
@@ -834,6 +838,7 @@ app.state.config.EXTERNAL_DOCUMENT_LOADER_URL = EXTERNAL_DOCUMENT_LOADER_URL
app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY = EXTERNAL_DOCUMENT_LOADER_API_KEY
app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL
app.state.config.DOCLING_SERVER_URL = DOCLING_SERVER_URL
+app.state.config.DOCLING_PARAMS = DOCLING_PARAMS
app.state.config.DOCLING_DO_OCR = DOCLING_DO_OCR
app.state.config.DOCLING_FORCE_OCR = DOCLING_FORCE_OCR
app.state.config.DOCLING_OCR_ENGINE = DOCLING_OCR_ENGINE
@@ -1095,11 +1100,15 @@ app.state.config.AUDIO_STT_AZURE_LOCALES = AUDIO_STT_AZURE_LOCALES
app.state.config.AUDIO_STT_AZURE_BASE_URL = AUDIO_STT_AZURE_BASE_URL
app.state.config.AUDIO_STT_AZURE_MAX_SPEAKERS = AUDIO_STT_AZURE_MAX_SPEAKERS
-app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL
-app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY
app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE
+
app.state.config.TTS_MODEL = AUDIO_TTS_MODEL
app.state.config.TTS_VOICE = AUDIO_TTS_VOICE
+
+app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL
+app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY
+app.state.config.TTS_OPENAI_PARAMS = AUDIO_TTS_OPENAI_PARAMS
+
app.state.config.TTS_API_KEY = AUDIO_TTS_API_KEY
app.state.config.TTS_SPLIT_ON = AUDIO_TTS_SPLIT_ON
@@ -1170,12 +1179,32 @@ class RedirectMiddleware(BaseHTTPMiddleware):
path = request.url.path
query_params = dict(parse_qs(urlparse(str(request.url)).query))
+ redirect_params = {}
+
# Check for the specific watch path and the presence of 'v' parameter
if path.endswith("/watch") and "v" in query_params:
# Extract the first 'v' parameter
- video_id = query_params["v"][0]
- encoded_video_id = urlencode({"youtube": video_id})
- redirect_url = f"/?{encoded_video_id}"
+ youtube_video_id = query_params["v"][0]
+ redirect_params["youtube"] = youtube_video_id
+
+ if "shared" in query_params and len(query_params["shared"]) > 0:
+ # PWA share_target support
+
+ text = query_params["shared"][0]
+ if text:
+ urls = re.match(r"https://\S+", text)
+ if urls:
+ from open_webui.retrieval.loaders.youtube import _parse_video_id
+
+ if youtube_video_id := _parse_video_id(urls[0]):
+ redirect_params["youtube"] = youtube_video_id
+ else:
+ redirect_params["load-url"] = urls[0]
+ else:
+ redirect_params["q"] = text
+
+ if redirect_params:
+ redirect_url = f"/?{urlencode(redirect_params)}"
return RedirectResponse(url=redirect_url)
# Proceed with the normal flow of other requests
@@ -1474,7 +1503,7 @@ async def chat_completion(
}
if metadata.get("chat_id") and (user and user.role != "admin"):
- if metadata["chat_id"] != "local":
+ if not metadata["chat_id"].startswith("local:"):
chat = Chats.get_chat_by_id_and_user_id(metadata["chat_id"], user.id)
if chat is None:
raise HTTPException(
@@ -1501,13 +1530,14 @@ async def chat_completion(
response = await chat_completion_handler(request, form_data, user)
if metadata.get("chat_id") and metadata.get("message_id"):
try:
- Chats.upsert_message_to_chat_by_id_and_message_id(
- metadata["chat_id"],
- metadata["message_id"],
- {
- "model": model_id,
- },
- )
+ if not metadata["chat_id"].startswith("local:"):
+ Chats.upsert_message_to_chat_by_id_and_message_id(
+ metadata["chat_id"],
+ metadata["message_id"],
+ {
+ "model": model_id,
+ },
+ )
except:
pass
@@ -1528,13 +1558,14 @@ async def chat_completion(
if metadata.get("chat_id") and metadata.get("message_id"):
# Update the chat message with the error
try:
- Chats.upsert_message_to_chat_by_id_and_message_id(
- metadata["chat_id"],
- metadata["message_id"],
- {
- "error": {"content": str(e)},
- },
- )
+ if not metadata["chat_id"].startswith("local:"):
+ Chats.upsert_message_to_chat_by_id_and_message_id(
+ metadata["chat_id"],
+ metadata["message_id"],
+ {
+ "error": {"content": str(e)},
+ },
+ )
event_emitter = get_event_emitter(metadata)
await event_emitter(
@@ -1903,13 +1934,20 @@ if len(app.state.config.TOOL_SERVER_CONNECTIONS) > 0:
"oauth_client_info", ""
)
- oauth_client_info = decrypt_data(oauth_client_info)
- app.state.oauth_client_manager.add_client(
- f"mcp:{server_id}", OAuthClientInformationFull(**oauth_client_info)
- )
+ try:
+ oauth_client_info = decrypt_data(oauth_client_info)
+ app.state.oauth_client_manager.add_client(
+ f"mcp:{server_id}",
+ OAuthClientInformationFull(**oauth_client_info),
+ )
+ except Exception as e:
+ log.error(
+ f"Error adding OAuth client for MCP tool server {server_id}: {e}"
+ )
+ pass
try:
- if REDIS_URL:
+ if ENABLE_STAR_SESSIONS_MIDDLEWARE:
redis_session_store = RedisStore(
url=REDIS_URL,
prefix=(f"{REDIS_KEY_PREFIX}:session:" if REDIS_KEY_PREFIX else "session:"),
@@ -2004,6 +2042,11 @@ async def get_manifest_json():
"purpose": "maskable",
},
],
+ "share_target": {
+ "action": "/",
+ "method": "GET",
+ "params": {"text": "shared"},
+ },
}
diff --git a/backend/open_webui/models/files.py b/backend/open_webui/models/files.py
index bf07b5f86f..c5cbaf91f8 100644
--- a/backend/open_webui/models/files.py
+++ b/backend/open_webui/models/files.py
@@ -186,7 +186,9 @@ class FilesTable:
created_at=file.created_at,
updated_at=file.updated_at,
)
- for file in db.query(File)
+ for file in db.query(
+ File.id, File.meta, File.created_at, File.updated_at
+ )
.filter(File.id.in_(ids))
.order_by(File.updated_at.desc())
.all()
diff --git a/backend/open_webui/models/functions.py b/backend/open_webui/models/functions.py
index e8ce3aa811..2020a29633 100644
--- a/backend/open_webui/models/functions.py
+++ b/backend/open_webui/models/functions.py
@@ -3,7 +3,7 @@ import time
from typing import Optional
from open_webui.internal.db import Base, JSONField, get_db
-from open_webui.models.users import Users
+from open_webui.models.users import Users, UserModel
from open_webui.env import SRC_LOG_LEVELS
from pydantic import BaseModel, ConfigDict
from sqlalchemy import BigInteger, Boolean, Column, String, Text, Index
@@ -76,6 +76,10 @@ class FunctionWithValvesModel(BaseModel):
####################
+class FunctionUserResponse(FunctionModel):
+ user: Optional[UserModel] = None
+
+
class FunctionResponse(BaseModel):
id: str
user_id: str
@@ -203,6 +207,28 @@ class FunctionsTable:
FunctionModel.model_validate(function) for function in functions
]
+ def get_function_list(self) -> list[FunctionUserResponse]:
+ with get_db() as db:
+ functions = db.query(Function).order_by(Function.updated_at.desc()).all()
+ user_ids = list(set(func.user_id for func in functions))
+
+ users = Users.get_users_by_user_ids(user_ids) if user_ids else []
+ users_dict = {user.id: user for user in users}
+
+ return [
+ FunctionUserResponse.model_validate(
+ {
+ **FunctionModel.model_validate(func).model_dump(),
+ "user": (
+ users_dict.get(func.user_id).model_dump()
+ if func.user_id in users_dict
+ else None
+ ),
+ }
+ )
+ for func in functions
+ ]
+
def get_functions_by_type(
self, type: str, active_only=False
) -> list[FunctionModel]:
diff --git a/backend/open_webui/retrieval/loaders/main.py b/backend/open_webui/retrieval/loaders/main.py
index 45f3d8c941..b3d90cc8f3 100644
--- a/backend/open_webui/retrieval/loaders/main.py
+++ b/backend/open_webui/retrieval/loaders/main.py
@@ -346,11 +346,9 @@ class Loader:
self.engine == "document_intelligence"
and self.kwargs.get("DOCUMENT_INTELLIGENCE_ENDPOINT") != ""
and (
- file_ext in ["pdf", "xls", "xlsx", "docx", "ppt", "pptx"]
+ file_ext in ["pdf", "docx", "ppt", "pptx"]
or file_content_type
in [
- "application/vnd.ms-excel",
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
diff --git a/backend/open_webui/retrieval/loaders/youtube.py b/backend/open_webui/retrieval/loaders/youtube.py
index 360ef0a6c7..da17eaef65 100644
--- a/backend/open_webui/retrieval/loaders/youtube.py
+++ b/backend/open_webui/retrieval/loaders/youtube.py
@@ -157,3 +157,10 @@ class YoutubeLoader:
f"No transcript found for any of the specified languages: {languages_tried}. Verify if the video has transcripts, add more languages if needed."
)
raise NoTranscriptFound(self.video_id, self.language, list(transcript_list))
+
+ async def aload(self) -> Generator[Document, None, None]:
+ """Asynchronously load YouTube transcripts into `Document` objects."""
+ import asyncio
+
+ loop = asyncio.get_event_loop()
+ return await loop.run_in_executor(None, self.load)
diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py
index 65da1592e1..66a7fa2ddf 100644
--- a/backend/open_webui/retrieval/utils.py
+++ b/backend/open_webui/retrieval/utils.py
@@ -6,6 +6,7 @@ import requests
import hashlib
from concurrent.futures import ThreadPoolExecutor
import time
+import re
from urllib.parse import quote
from huggingface_hub import snapshot_download
@@ -16,6 +17,7 @@ from langchain_core.documents import Document
from open_webui.config import VECTOR_DB
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
+
from open_webui.models.users import UserModel
from open_webui.models.files import Files
from open_webui.models.knowledge import Knowledges
@@ -27,6 +29,9 @@ from open_webui.retrieval.vector.main import GetResult
from open_webui.utils.access_control import has_access
from open_webui.utils.misc import get_message_list
+from open_webui.retrieval.web.utils import get_web_loader
+from open_webui.retrieval.loaders.youtube import YoutubeLoader
+
from open_webui.env import (
SRC_LOG_LEVELS,
@@ -49,6 +54,33 @@ from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.retrievers import BaseRetriever
+def is_youtube_url(url: str) -> bool:
+ youtube_regex = r"^(https?://)?(www\.)?(youtube\.com|youtu\.be)/.+$"
+ return re.match(youtube_regex, url) is not None
+
+
+def get_loader(request, url: str):
+ if is_youtube_url(url):
+ return YoutubeLoader(
+ url,
+ language=request.app.state.config.YOUTUBE_LOADER_LANGUAGE,
+ proxy_url=request.app.state.config.YOUTUBE_LOADER_PROXY_URL,
+ )
+ else:
+ return get_web_loader(
+ url,
+ verify_ssl=request.app.state.config.ENABLE_WEB_LOADER_SSL_VERIFICATION,
+ requests_per_second=request.app.state.config.WEB_LOADER_CONCURRENT_REQUESTS,
+ )
+
+
+def get_content_from_url(request, url: str) -> str:
+ loader = get_loader(request, url)
+ docs = loader.load()
+ content = " ".join([doc.page_content for doc in docs])
+ return content, docs
+
+
class VectorSearchRetriever(BaseRetriever):
collection_name: Any
embedding_function: Any
@@ -188,7 +220,11 @@ def query_doc_with_hybrid_search(
zip(distances, metadatas, documents), key=lambda x: x[0], reverse=True
)
sorted_items = sorted_items[:k]
- distances, documents, metadatas = map(list, zip(*sorted_items))
+
+ if sorted_items:
+ distances, documents, metadatas = map(list, zip(*sorted_items))
+ else:
+ distances, documents, metadatas = [], [], []
result = {
"distances": [distances],
@@ -571,6 +607,13 @@ def get_sources_from_items(
"metadatas": [[{"file_id": chat.id, "name": chat.title}]],
}
+ elif item.get("type") == "url":
+ content, docs = get_content_from_url(request, item.get("url"))
+ if docs:
+ query_result = {
+ "documents": [[content]],
+ "metadatas": [[{"url": item.get("url"), "name": item.get("url")}]],
+ }
elif item.get("type") == "file":
if (
item.get("context") == "full"
@@ -736,7 +779,6 @@ def get_sources_from_items(
sources.append(source)
except Exception as e:
log.exception(e)
-
return sources
diff --git a/backend/open_webui/retrieval/web/utils.py b/backend/open_webui/retrieval/web/utils.py
index 5ba27ee8f0..61356adb56 100644
--- a/backend/open_webui/retrieval/web/utils.py
+++ b/backend/open_webui/retrieval/web/utils.py
@@ -75,7 +75,8 @@ def safe_validate_urls(url: Sequence[str]) -> Sequence[str]:
try:
if validate_url(u):
valid_urls.append(u)
- except ValueError:
+ except Exception as e:
+ log.debug(f"Invalid URL {u}: {str(e)}")
continue
return valid_urls
diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py
index 100610a83a..cb7a57b5b7 100644
--- a/backend/open_webui/routers/audio.py
+++ b/backend/open_webui/routers/audio.py
@@ -3,6 +3,7 @@ import json
import logging
import os
import uuid
+import html
from functools import lru_cache
from pydub import AudioSegment
from pydub.silence import split_on_silence
@@ -153,6 +154,7 @@ def set_faster_whisper_model(model: str, auto_update: bool = False):
class TTSConfigForm(BaseModel):
OPENAI_API_BASE_URL: str
OPENAI_API_KEY: str
+ OPENAI_PARAMS: Optional[dict] = None
API_KEY: str
ENGINE: str
MODEL: str
@@ -189,6 +191,7 @@ async def get_audio_config(request: Request, user=Depends(get_admin_user)):
"tts": {
"OPENAI_API_BASE_URL": request.app.state.config.TTS_OPENAI_API_BASE_URL,
"OPENAI_API_KEY": request.app.state.config.TTS_OPENAI_API_KEY,
+ "OPENAI_PARAMS": request.app.state.config.TTS_OPENAI_PARAMS,
"API_KEY": request.app.state.config.TTS_API_KEY,
"ENGINE": request.app.state.config.TTS_ENGINE,
"MODEL": request.app.state.config.TTS_MODEL,
@@ -221,6 +224,7 @@ async def update_audio_config(
):
request.app.state.config.TTS_OPENAI_API_BASE_URL = form_data.tts.OPENAI_API_BASE_URL
request.app.state.config.TTS_OPENAI_API_KEY = form_data.tts.OPENAI_API_KEY
+ request.app.state.config.TTS_OPENAI_PARAMS = form_data.tts.OPENAI_PARAMS
request.app.state.config.TTS_API_KEY = form_data.tts.API_KEY
request.app.state.config.TTS_ENGINE = form_data.tts.ENGINE
request.app.state.config.TTS_MODEL = form_data.tts.MODEL
@@ -261,12 +265,13 @@ async def update_audio_config(
return {
"tts": {
- "OPENAI_API_BASE_URL": request.app.state.config.TTS_OPENAI_API_BASE_URL,
- "OPENAI_API_KEY": request.app.state.config.TTS_OPENAI_API_KEY,
- "API_KEY": request.app.state.config.TTS_API_KEY,
"ENGINE": request.app.state.config.TTS_ENGINE,
"MODEL": request.app.state.config.TTS_MODEL,
"VOICE": request.app.state.config.TTS_VOICE,
+ "OPENAI_API_BASE_URL": request.app.state.config.TTS_OPENAI_API_BASE_URL,
+ "OPENAI_API_KEY": request.app.state.config.TTS_OPENAI_API_KEY,
+ "OPENAI_PARAMS": request.app.state.config.TTS_OPENAI_PARAMS,
+ "API_KEY": request.app.state.config.TTS_API_KEY,
"SPLIT_ON": request.app.state.config.TTS_SPLIT_ON,
"AZURE_SPEECH_REGION": request.app.state.config.TTS_AZURE_SPEECH_REGION,
"AZURE_SPEECH_BASE_URL": request.app.state.config.TTS_AZURE_SPEECH_BASE_URL,
@@ -336,6 +341,11 @@ async def speech(request: Request, user=Depends(get_verified_user)):
async with aiohttp.ClientSession(
timeout=timeout, trust_env=True
) as session:
+ payload = {
+ **payload,
+ **(request.app.state.config.TTS_OPENAI_PARAMS or {}),
+ }
+
r = await session.post(
url=f"{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/speech",
json=payload,
@@ -458,7 +468,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
try:
data = f"""
{code}
{/if}
+ {:else if lang === 'vega' || lang === 'vega-lite'}
+ {#if vegaHtml}
+ {code}
+ {/if}
{:else}
{/if}
diff --git a/src/lib/components/chat/Messages/ResponseMessage/RegenerateMenu.svelte b/src/lib/components/chat/Messages/ResponseMessage/RegenerateMenu.svelte
index ba822b77a1..8adca15283 100644
--- a/src/lib/components/chat/Messages/ResponseMessage/RegenerateMenu.svelte
+++ b/src/lib/components/chat/Messages/ResponseMessage/RegenerateMenu.svelte
@@ -29,7 +29,7 @@
<\/p>/g, '
')
- .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
- )
- .replace(/\u00a0/g, ' ');
+
+ if (richText) {
+ mdValue = turndownService
+ .turndown(
+ htmlValue
+ .replace(/
<\/p>/g, '
')
+ .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
+ )
+ .replace(/\u00a0/g, ' ');
+ } else {
+ mdValue = turndownService
+ .turndown(
+ htmlValue
+ // Replace empty paragraphs with line breaks
+ .replace(/
<\/p>/g, '
')
+ // Replace multiple spaces with non-breaking spaces
+ .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
+ // Replace tabs with non-breaking spaces (preserve indentation)
+ .replace(/\t/g, '\u00a0\u00a0\u00a0\u00a0') // 1 tab = 4 spaces
+ )
+ // Convert non-breaking spaces back to regular spaces for markdown
+ .replace(/\u00a0/g, ' ');
+ }
onChange({
html: htmlValue,
@@ -1035,10 +1045,15 @@
if (!event.clipboardData) return false;
if (richText) return false; // Let ProseMirror handle normal copy in rich text mode
- const plain = editor.getText();
- const html = editor.getHTML();
+ const { state } = view;
+ const { from, to } = state.selection;
- event.clipboardData.setData('text/plain', plain.replaceAll('\n\n', '\n'));
+ // Only take the selected text & HTML, not the full doc
+ const plain = state.doc.textBetween(from, to, '\n');
+ const slice = state.doc.cut(from, to);
+ const html = editor.schema ? editor.getHTML(slice) : editor.getHTML(); // depending on your editor API
+
+ event.clipboardData.setData('text/plain', plain);
event.clipboardData.setData('text/html', html);
event.preventDefault();
diff --git a/src/lib/components/common/Switch.svelte b/src/lib/components/common/Switch.svelte
index c04d9b2ef4..c71ddb0733 100644
--- a/src/lib/components/common/Switch.svelte
+++ b/src/lib/components/common/Switch.svelte
@@ -1,7 +1,9 @@
+ {#if !$temporaryChatEnabled && chat?.id}
+
- {#if chat?.id}
-