Skip to content

Commit 883f1dd

Browse files
authored
Merge pull request open-webui#21346 from open-webui/dev
0.8.1
2 parents 7a7a257 + 12bad45 commit 883f1dd

39 files changed

Lines changed: 1264 additions & 498 deletions

CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ 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.8.1] - 2026-02-14
9+
10+
### Added
11+
12+
- 🚀 **Channel user active status.** Checking user active status in channels is now faster thanks to optimized database queries. [Commit](https://github.com/open-webui/open-webui/commit/ca6b18ab5cb94153a9dae233f975d36bf6b19b76)
13+
- 🔗 **Responses API endpoint with model routing.** The OpenAI API proxy now supports a /responses endpoint that routes requests to the correct backend based on the model field in the request, instead of always using the first configured endpoint. This enables support for backends like vLLM that provide /skills and /v1/responses endpoints. [Commit](https://github.com/open-webui/open-webui/commit/abc9b63093d65f4d74342db85b7d5df1809aa0f0), [Commit](https://github.com/open-webui/open-webui/commit/79ecbfc757f0642740d0e44fab98263d84295490)
14+
- ⚡ **Model and prompt list optimization.** Improved performance when loading models and prompts by pre-fetching user group IDs once instead of making multiple database queries. [Commit](https://github.com/open-webui/open-webui/commit/20de5a87da0c12e4052b50887a42ddd7228c5ef5)
15+
- 🗄️ **Batch access control queries.** Improved performance when loading models, prompts, and knowledge bases by replacing multiple individual access checks with single batch queries, significantly reducing database load for large deployments. [Commit](https://github.com/open-webui/open-webui/commit/589c4e64c1b7bb7a7a5abc20382b92fb860e28c2)
16+
- 💨 **Faster user list loading.** User lists now load significantly faster by deferring profile image loading; images are fetched separately in parallel by the browser, improving caching and reducing database load. [Commit](https://github.com/open-webui/open-webui/commit/b7549d2f6ca2843661ec79a5a1e55da9e7553368)
17+
- 🔍 **Web search result count.** The built-in search_web tool now respects the admin-configured "Search Result Count" setting instead of always returning 5 results when using Native Function Calling mode. [#21373](https://github.com/open-webui/open-webui/pull/21373), [#21371](https://github.com/open-webui/open-webui/issues/21371)
18+
- 🔐 **SCIM externalId support.** SCIM-enabled deployments can now store and manage externalId for user provisioning, enabling better integration with identity providers like Microsoft Entra ID and Okta. [#21099](https://github.com/open-webui/open-webui/pull/21099), [#21280](https://github.com/open-webui/open-webui/issues/21280), [Commit](https://github.com/open-webui/open-webui/commit/d1d1efe212b16e0052359991d67fd813125077e8)
19+
- 🌐 **Translation updates.** Portuguese (Brazil) translations were updated.
20+
21+
### Fixed
22+
23+
- 🛡️ **Public sharing security fix.** Fixed a security issue where users with write access could see the Public sharing option regardless of their actual public sharing permission, and direct API calls could bypass frontend sharing restrictions. [#21358](https://github.com/open-webui/open-webui/pull/21358), [#21356](https://github.com/open-webui/open-webui/issues/21356)
24+
- 🔒 **Direct model access control fix.** Model access control changes now persist correctly for direct Ollama and OpenAI models that don't have database entries, and error messages display properly instead of showing "[object Object]". [Commit](https://github.com/open-webui/open-webui/commit/f027a01ab2ff3b6175af3dd13a4478c265c0544a), [#21377](https://github.com/open-webui/open-webui/issues/21377)
25+
- 💭 **Reasoning trace rendering performance.** Reasoning traces from models now render properly without being split into many fragments, preventing browser slowdowns during streaming responses. [#21348](https://github.com/open-webui/open-webui/issues/21348), [Commit](https://github.com/open-webui/open-webui/commit/3b61562c82448cf83710d8b6ed29b797991aa83a)
26+
- 🖥️ **ARM device compatibility fix.** Fixed an issue where upgrading to 0.8.0 would fail to start on ARM devices (like Raspberry Pi 4) due to torch 2.10.0 causing SIGILL errors; now pinned to torch<=2.9.1. [#21385](https://github.com/open-webui/open-webui/pull/21385), [#21349](https://github.com/open-webui/open-webui/issues/21349)
27+
- 🗄️ **Skills PostgreSQL compatibility fix.** Fixed a PostgreSQL compatibility issue where creating or listing skills would fail with a TypeError, while SQLite worked correctly. [#21372](https://github.com/open-webui/open-webui/pull/21372), [Commit](https://github.com/open-webui/open-webui/commit/b4c3f54f9648c4232a0fd6557703ffa66fcf4caa), [#21365](https://github.com/open-webui/open-webui/issues/21365)
28+
- 🗄️ **PostgreSQL analytics query fix.** Fixed an issue where retrieving chat IDs by model ID would fail on PostgreSQL due to incompatible DISTINCT ordering, while SQLite worked correctly. [#21347](https://github.com/open-webui/open-webui/issues/21347), [Commit](https://github.com/open-webui/open-webui/commit/7bda6bf767d5d5c4dc1111465096a88e10b5030e)
29+
- 🗃️ **SQLite cascade delete fix.** Deleting chats now properly removes all associated messages in SQLite, matching PostgreSQL behavior and preventing orphaned data. [#21362](https://github.com/open-webui/open-webui/pull/21362)
30+
- ☁️ **Ollama Cloud model naming fix.** Fixed an issue where using Ollama Cloud models would fail with "Model not found" errors because ":latest" was incorrectly appended to model names. [#21386](https://github.com/open-webui/open-webui/issues/21386)
31+
- 🛠️ **Knowledge selector tooltip z-index.** Fixed an issue where tooltips in the "Select Knowledge" dropdown were hidden behind the menu, making it difficult to read knowledge item names and descriptions. [#21375](https://github.com/open-webui/open-webui/pull/21375)
32+
- 🎯 **Model selector scroll position.** The model selector dropdown now correctly scrolls to and centers the currently selected model when opened, and resets scroll position when reopened. [Commit](https://github.com/open-webui/open-webui/commit/0b05b2fc7ed4c38af158707438ff404d1beb7c91)
33+
- 🐛 **Sync modal unexpected appearance.** Fixed an issue where the Sync Modal would appear unexpectedly after enabling the "Community Sharing" feature if the user had previously visited the app with the sync parameter. [#21376](https://github.com/open-webui/open-webui/pull/21376)
34+
- 🎨 **Knowledge collection layout fix.** Fixed a layout issue in the Knowledge integration menu where long collection names caused indentation artifacts and now properly truncate with ellipsis. [#21374](https://github.com/open-webui/open-webui/pull/21374)
35+
- 📝 **Metadata processing crash fix.** Fixed a latent bug where processing document metadata containing certain keys (content, pages, tables, paragraphs, sections, figures) would cause a RuntimeError due to dictionary mutation during iteration. [#21105](https://github.com/open-webui/open-webui/pull/21105)
36+
- 🔑 **Password validation regex fix.** Fixed the password validation regex by adding the raw string prefix, ensuring escape sequences like \d and \w are interpreted correctly. [#21400](https://github.com/open-webui/open-webui/pull/21400), [#21399](https://github.com/open-webui/open-webui/issues/21399)
37+
38+
### Changed
39+
40+
- ⚠️ **Database Migrations:** This release includes database schema changes; we strongly recommend backing up your database and all associated data before upgrading in production environments. If you are running a multi-worker, multi-server, or load-balanced deployment, all instances must be updated simultaneously, rolling updates are not supported and will cause application failures due to schema incompatibility.
41+
842
## [0.8.0] - 2026-02-12
943

1044
### Added

Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,16 @@ COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
137137
RUN pip3 install --no-cache-dir uv && \
138138
if [ "$USE_CUDA" = "true" ]; then \
139139
# If you use CUDA the whisper and embedding model will be downloaded on first use
140-
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
140+
# fix: pin torch<=2.9.1 - torch 2.10.0 aarch64 wheels cause SIGILL on ARM devices (RPi 4 Cortex-A72) #21349
141+
pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
141142
uv pip install --system -r requirements.txt --no-cache-dir && \
142143
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
143144
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')" && \
144145
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
145146
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
146147
python -c "import nltk; nltk.download('punkt_tab')"; \
147148
else \
148-
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
149+
pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
149150
uv pip install --system -r requirements.txt --no-cache-dir && \
150151
if [ "$USE_SLIM" != "true" ]; then \
151152
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \

backend/open_webui/env.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ def parse_section(section):
478478
)
479479
PASSWORD_VALIDATION_REGEX_PATTERN = os.environ.get(
480480
"PASSWORD_VALIDATION_REGEX_PATTERN",
481-
"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$",
481+
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$",
482482
)
483483

484484

@@ -572,6 +572,14 @@ def parse_section(section):
572572
== "true"
573573
)
574574
SCIM_TOKEN = os.environ.get("SCIM_TOKEN", "")
575+
SCIM_AUTH_PROVIDER = os.environ.get("SCIM_AUTH_PROVIDER", "")
576+
577+
if ENABLE_SCIM and not SCIM_AUTH_PROVIDER:
578+
log.warning(
579+
"SCIM is enabled but SCIM_AUTH_PROVIDER is not set. "
580+
"Set SCIM_AUTH_PROVIDER to the OAuth provider name (e.g. 'microsoft', 'oidc') "
581+
"to enable externalId storage."
582+
)
575583

576584
####################################
577585
# LICENSE_KEY
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""add scim column to user table
2+
3+
Revision ID: b2c3d4e5f6a7
4+
Revises: a1b2c3d4e5f6
5+
Create Date: 2026-02-13 14:19:00.000000
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = "b2c3d4e5f6a7"
16+
down_revision: Union[str, None] = "a1b2c3d4e5f6"
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
op.add_column("user", sa.Column("scim", sa.JSON(), nullable=True))
23+
24+
25+
def downgrade() -> None:
26+
op.drop_column("user", "scim")

backend/open_webui/models/access_grants.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,62 @@ def has_access(
515515
)
516516
return exists is not None
517517

518+
def get_accessible_resource_ids(
519+
self,
520+
user_id: str,
521+
resource_type: str,
522+
resource_ids: list[str],
523+
permission: str = "read",
524+
user_group_ids: Optional[set[str]] = None,
525+
db: Optional[Session] = None,
526+
) -> set[str]:
527+
"""
528+
Batch check: return the subset of resource_ids that the user can access.
529+
530+
This replaces calling has_access() in a loop (N+1) with a single query.
531+
"""
532+
if not resource_ids:
533+
return set()
534+
535+
with get_db_context(db) as db:
536+
conditions = [
537+
and_(
538+
AccessGrant.principal_type == "user",
539+
AccessGrant.principal_id == "*",
540+
),
541+
and_(
542+
AccessGrant.principal_type == "user",
543+
AccessGrant.principal_id == user_id,
544+
),
545+
]
546+
547+
if user_group_ids is None:
548+
from open_webui.models.groups import Groups
549+
550+
user_groups = Groups.get_groups_by_member_id(user_id, db=db)
551+
user_group_ids = {group.id for group in user_groups}
552+
553+
if user_group_ids:
554+
conditions.append(
555+
and_(
556+
AccessGrant.principal_type == "group",
557+
AccessGrant.principal_id.in_(user_group_ids),
558+
)
559+
)
560+
561+
rows = (
562+
db.query(AccessGrant.resource_id)
563+
.filter(
564+
AccessGrant.resource_type == resource_type,
565+
AccessGrant.resource_id.in_(resource_ids),
566+
AccessGrant.permission == permission,
567+
or_(*conditions),
568+
)
569+
.distinct()
570+
.all()
571+
)
572+
return {row[0] for row in rows}
573+
518574
def get_users_with_access(
519575
self,
520576
resource_type: str,

backend/open_webui/models/chat_messages.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Text,
1616
JSON,
1717
Index,
18+
func,
1819
)
1920

2021
####################
@@ -279,25 +280,26 @@ def get_chat_ids_by_model_id(
279280
db: Optional[Session] = None,
280281
) -> list[str]:
281282
"""Get distinct chat_ids that used a specific model."""
282-
from sqlalchemy import distinct
283283

284284
with get_db_context(db) as db:
285-
query = db.query(distinct(ChatMessage.chat_id)).filter(
286-
ChatMessage.model_id == model_id
287-
)
285+
query = db.query(
286+
ChatMessage.chat_id,
287+
func.max(ChatMessage.created_at).label("last_message_at"),
288+
).filter(ChatMessage.model_id == model_id)
288289
if start_date:
289290
query = query.filter(ChatMessage.created_at >= start_date)
290291
if end_date:
291292
query = query.filter(ChatMessage.created_at <= end_date)
292293

293-
# Order by most recent message in each chat
294+
# Group by chat_id and order by most recent message in each chat
294295
chat_ids = (
295-
query.order_by(ChatMessage.created_at.desc())
296+
query.group_by(ChatMessage.chat_id)
297+
.order_by(func.max(ChatMessage.created_at).desc())
296298
.offset(skip)
297299
.limit(limit)
298300
.all()
299301
)
300-
return [chat_id for (chat_id,) in chat_ids]
302+
return [chat_id for chat_id, _ in chat_ids]
301303

302304
def delete_messages_by_chat_id(
303305
self, chat_id: str, db: Optional[Session] = None

backend/open_webui/models/chats.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
99
from open_webui.models.tags import TagModel, Tag, Tags
1010
from open_webui.models.folders import Folders
11-
from open_webui.models.chat_messages import ChatMessages
11+
from open_webui.models.chat_messages import ChatMessage, ChatMessages
1212
from open_webui.utils.misc import sanitize_data_for_db, sanitize_text_for_db
1313

1414
from pydantic import BaseModel, ConfigDict
@@ -621,6 +621,13 @@ def delete_shared_chat_by_chat_id(
621621
) -> bool:
622622
try:
623623
with get_db_context(db) as db:
624+
# Use subquery to delete chat_messages for shared chats
625+
shared_chat_id_subquery = (
626+
db.query(Chat.id).filter_by(user_id=f"shared-{chat_id}").subquery()
627+
)
628+
db.query(ChatMessage).filter(
629+
ChatMessage.chat_id.in_(shared_chat_id_subquery)
630+
).delete(synchronize_session=False)
624631
db.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete()
625632
db.commit()
626633

@@ -1410,6 +1417,7 @@ def delete_all_tags_by_id_and_user_id(
14101417
def delete_chat_by_id(self, id: str, db: Optional[Session] = None) -> bool:
14111418
try:
14121419
with get_db_context(db) as db:
1420+
db.query(ChatMessage).filter_by(chat_id=id).delete()
14131421
db.query(Chat).filter_by(id=id).delete()
14141422
db.commit()
14151423

@@ -1422,6 +1430,7 @@ def delete_chat_by_id_and_user_id(
14221430
) -> bool:
14231431
try:
14241432
with get_db_context(db) as db:
1433+
db.query(ChatMessage).filter_by(chat_id=id).delete()
14251434
db.query(Chat).filter_by(id=id, user_id=user_id).delete()
14261435
db.commit()
14271436

@@ -1436,6 +1445,12 @@ def delete_chats_by_user_id(
14361445
with get_db_context(db) as db:
14371446
self.delete_shared_chats_by_user_id(user_id, db=db)
14381447

1448+
chat_id_subquery = (
1449+
db.query(Chat.id).filter_by(user_id=user_id).subquery()
1450+
)
1451+
db.query(ChatMessage).filter(
1452+
ChatMessage.chat_id.in_(chat_id_subquery)
1453+
).delete(synchronize_session=False)
14391454
db.query(Chat).filter_by(user_id=user_id).delete()
14401455
db.commit()
14411456

@@ -1448,6 +1463,14 @@ def delete_chats_by_user_id_and_folder_id(
14481463
) -> bool:
14491464
try:
14501465
with get_db_context(db) as db:
1466+
chat_id_subquery = (
1467+
db.query(Chat.id)
1468+
.filter_by(user_id=user_id, folder_id=folder_id)
1469+
.subquery()
1470+
)
1471+
db.query(ChatMessage).filter(
1472+
ChatMessage.chat_id.in_(chat_id_subquery)
1473+
).delete(synchronize_session=False)
14511474
db.query(Chat).filter_by(user_id=user_id, folder_id=folder_id).delete()
14521475
db.commit()
14531476

@@ -1481,6 +1504,15 @@ def delete_shared_chats_by_user_id(
14811504
chats_by_user = db.query(Chat).filter_by(user_id=user_id).all()
14821505
shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user]
14831506

1507+
# Use subquery to delete chat_messages for shared chats
1508+
shared_id_subq = (
1509+
db.query(Chat.id)
1510+
.filter(Chat.user_id.in_(shared_chat_ids))
1511+
.subquery()
1512+
)
1513+
db.query(ChatMessage).filter(
1514+
ChatMessage.chat_id.in_(shared_id_subq)
1515+
).delete(synchronize_session=False)
14841516
db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete()
14851517
db.commit()
14861518

backend/open_webui/models/skills.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from typing import Optional
44

55
from sqlalchemy.orm import Session
6-
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
6+
from open_webui.internal.db import Base, get_db, get_db_context
77
from open_webui.models.users import Users, UserResponse
88
from open_webui.models.groups import Groups
99
from open_webui.models.access_grants import AccessGrantModel, AccessGrants
1010

1111
from pydantic import BaseModel, ConfigDict, Field
12-
from sqlalchemy import BigInteger, Boolean, Column, String, Text, or_
12+
from sqlalchemy import JSON, BigInteger, Boolean, Column, String, Text, or_
1313

1414
log = logging.getLogger(__name__)
1515

@@ -26,7 +26,7 @@ class Skill(Base):
2626
name = Column(Text, unique=True)
2727
description = Column(Text, nullable=True)
2828
content = Column(Text)
29-
meta = Column(JSONField)
29+
meta = Column(JSON)
3030
is_active = Column(Boolean, default=True)
3131

3232
updated_at = Column(BigInteger)

0 commit comments

Comments
 (0)