Skip to content

Commit 6f1486f

Browse files
authored
Merge pull request open-webui#19466 from open-webui/dev
0.6.41
2 parents 140605e + 73f7e91 commit 6f1486f

File tree

229 files changed

+8854
-2135
lines changed

Some content is hidden

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

229 files changed

+8854
-2135
lines changed

CHANGELOG.md

Lines changed: 73 additions & 0 deletions
Large diffs are not rendered by default.

backend/open_webui/config.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -583,14 +583,16 @@ def __getattr__(self, key):
583583
os.environ.get("OAUTH_ROLES_CLAIM", "roles"),
584584
)
585585

586-
SEP = os.environ.get("OAUTH_ROLES_SEPARATOR", ",")
586+
OAUTH_ROLES_SEPARATOR = os.environ.get("OAUTH_ROLES_SEPARATOR", ",")
587587

588588
OAUTH_ALLOWED_ROLES = PersistentConfig(
589589
"OAUTH_ALLOWED_ROLES",
590590
"oauth.allowed_roles",
591591
[
592592
role.strip()
593-
for role in os.environ.get("OAUTH_ALLOWED_ROLES", f"user{SEP}admin").split(SEP)
593+
for role in os.environ.get(
594+
"OAUTH_ALLOWED_ROLES", f"user{OAUTH_ROLES_SEPARATOR}admin"
595+
).split(OAUTH_ROLES_SEPARATOR)
594596
if role
595597
],
596598
)
@@ -600,7 +602,9 @@ def __getattr__(self, key):
600602
"oauth.admin_roles",
601603
[
602604
role.strip()
603-
for role in os.environ.get("OAUTH_ADMIN_ROLES", "admin").split(SEP)
605+
for role in os.environ.get("OAUTH_ADMIN_ROLES", "admin").split(
606+
OAUTH_ROLES_SEPARATOR
607+
)
604608
if role
605609
],
606610
)
@@ -1443,10 +1447,18 @@ def feishu_oauth_register(oauth: OAuth):
14431447
== "true"
14441448
)
14451449

1450+
USER_PERMISSIONS_FEATURES_FOLDERS = (
1451+
os.environ.get("USER_PERMISSIONS_FEATURES_FOLDERS", "True").lower() == "true"
1452+
)
1453+
14461454
USER_PERMISSIONS_FEATURES_NOTES = (
14471455
os.environ.get("USER_PERMISSIONS_FEATURES_NOTES", "True").lower() == "true"
14481456
)
14491457

1458+
USER_PERMISSIONS_FEATURES_CHANNELS = (
1459+
os.environ.get("USER_PERMISSIONS_FEATURES_CHANNELS", "True").lower() == "true"
1460+
)
1461+
14501462
USER_PERMISSIONS_FEATURES_API_KEYS = (
14511463
os.environ.get("USER_PERMISSIONS_FEATURES_API_KEYS", "False").lower() == "true"
14521464
)
@@ -1499,12 +1511,16 @@ def feishu_oauth_register(oauth: OAuth):
14991511
"temporary_enforced": USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED,
15001512
},
15011513
"features": {
1514+
# General features
15021515
"api_keys": USER_PERMISSIONS_FEATURES_API_KEYS,
1516+
"notes": USER_PERMISSIONS_FEATURES_NOTES,
1517+
"folders": USER_PERMISSIONS_FEATURES_FOLDERS,
1518+
"channels": USER_PERMISSIONS_FEATURES_CHANNELS,
15031519
"direct_tool_servers": USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS,
1520+
# Chat features
15041521
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
15051522
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
15061523
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
1507-
"notes": USER_PERMISSIONS_FEATURES_NOTES,
15081524
},
15091525
}
15101526

@@ -1514,6 +1530,12 @@ def feishu_oauth_register(oauth: OAuth):
15141530
DEFAULT_USER_PERMISSIONS,
15151531
)
15161532

1533+
ENABLE_FOLDERS = PersistentConfig(
1534+
"ENABLE_FOLDERS",
1535+
"folders.enable",
1536+
os.environ.get("ENABLE_FOLDERS", "True").lower() == "true",
1537+
)
1538+
15171539
ENABLE_CHANNELS = PersistentConfig(
15181540
"ENABLE_CHANNELS",
15191541
"channels.enable",
@@ -2568,6 +2590,12 @@ class BannerModel(BaseModel):
25682590
os.getenv("DOCUMENT_INTELLIGENCE_KEY", ""),
25692591
)
25702592

2593+
DOCUMENT_INTELLIGENCE_MODEL = PersistentConfig(
2594+
"DOCUMENT_INTELLIGENCE_MODEL",
2595+
"rag.document_intelligence_model",
2596+
os.getenv("DOCUMENT_INTELLIGENCE_MODEL", "prebuilt-layout"),
2597+
)
2598+
25712599
MISTRAL_OCR_API_BASE_URL = PersistentConfig(
25722600
"MISTRAL_OCR_API_BASE_URL",
25732601
"rag.MISTRAL_OCR_API_BASE_URL",

backend/open_webui/main.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@
6161
from open_webui.utils.audit import AuditLevel, AuditLoggingMiddleware
6262
from open_webui.utils.logger import start_logger
6363
from open_webui.socket.main import (
64+
MODELS,
6465
app as socket_app,
6566
periodic_usage_pool_cleanup,
6667
get_event_emitter,
6768
get_models_in_use,
68-
get_active_user_ids,
6969
)
7070
from open_webui.routers import (
7171
audio,
@@ -273,6 +273,7 @@
273273
DOCLING_PARAMS,
274274
DOCUMENT_INTELLIGENCE_ENDPOINT,
275275
DOCUMENT_INTELLIGENCE_KEY,
276+
DOCUMENT_INTELLIGENCE_MODEL,
276277
MISTRAL_OCR_API_BASE_URL,
277278
MISTRAL_OCR_API_KEY,
278279
RAG_TEXT_SPLITTER,
@@ -352,6 +353,7 @@
352353
ENABLE_API_KEYS,
353354
ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS,
354355
API_KEYS_ALLOWED_ENDPOINTS,
356+
ENABLE_FOLDERS,
355357
ENABLE_CHANNELS,
356358
ENABLE_NOTES,
357359
ENABLE_COMMUNITY_SHARING,
@@ -767,6 +769,7 @@ async def lifespan(app: FastAPI):
767769
app.state.config.BANNERS = WEBUI_BANNERS
768770

769771

772+
app.state.config.ENABLE_FOLDERS = ENABLE_FOLDERS
770773
app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS
771774
app.state.config.ENABLE_NOTES = ENABLE_NOTES
772775
app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
@@ -869,6 +872,7 @@ async def lifespan(app: FastAPI):
869872
app.state.config.DOCLING_PARAMS = DOCLING_PARAMS
870873
app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT = DOCUMENT_INTELLIGENCE_ENDPOINT
871874
app.state.config.DOCUMENT_INTELLIGENCE_KEY = DOCUMENT_INTELLIGENCE_KEY
875+
app.state.config.DOCUMENT_INTELLIGENCE_MODEL = DOCUMENT_INTELLIGENCE_MODEL
872876
app.state.config.MISTRAL_OCR_API_BASE_URL = MISTRAL_OCR_API_BASE_URL
873877
app.state.config.MISTRAL_OCR_API_KEY = MISTRAL_OCR_API_KEY
874878
app.state.config.MINERU_API_MODE = MINERU_API_MODE
@@ -980,9 +984,7 @@ async def lifespan(app: FastAPI):
980984

981985
try:
982986
app.state.ef = get_ef(
983-
app.state.config.RAG_EMBEDDING_ENGINE,
984-
app.state.config.RAG_EMBEDDING_MODEL,
985-
RAG_EMBEDDING_MODEL_AUTO_UPDATE,
987+
app.state.config.RAG_EMBEDDING_ENGINE, app.state.config.RAG_EMBEDDING_MODEL
986988
)
987989
if (
988990
app.state.config.ENABLE_RAG_HYBRID_SEARCH
@@ -993,7 +995,6 @@ async def lifespan(app: FastAPI):
993995
app.state.config.RAG_RERANKING_MODEL,
994996
app.state.config.RAG_EXTERNAL_RERANKER_URL,
995997
app.state.config.RAG_EXTERNAL_RERANKER_API_KEY,
996-
RAG_RERANKING_MODEL_AUTO_UPDATE,
997998
)
998999
else:
9991000
app.state.rf = None
@@ -1215,7 +1216,7 @@ async def lifespan(app: FastAPI):
12151216
#
12161217
########################################
12171218

1218-
app.state.MODELS = {}
1219+
app.state.MODELS = MODELS
12191220

12201221
# Add the middleware to the app
12211222
if ENABLE_COMPRESSION_MIDDLEWARE:
@@ -1575,6 +1576,7 @@ async def chat_completion(
15751576
"user_id": user.id,
15761577
"chat_id": form_data.pop("chat_id", None),
15771578
"message_id": form_data.pop("id", None),
1579+
"parent_message_id": form_data.pop("parent_id", None),
15781580
"session_id": form_data.pop("session_id", None),
15791581
"filter_ids": form_data.pop("filter_ids", []),
15801582
"tool_ids": form_data.get("tool_ids", None),
@@ -1631,6 +1633,7 @@ async def process_chat(request, form_data, user, metadata, model):
16311633
metadata["chat_id"],
16321634
metadata["message_id"],
16331635
{
1636+
"parentId": metadata.get("parent_message_id", None),
16341637
"model": model_id,
16351638
},
16361639
)
@@ -1663,6 +1666,7 @@ async def process_chat(request, form_data, user, metadata, model):
16631666
metadata["chat_id"],
16641667
metadata["message_id"],
16651668
{
1669+
"parentId": metadata.get("parent_message_id", None),
16661670
"error": {"content": str(e)},
16671671
},
16681672
)
@@ -1842,6 +1846,7 @@ async def get_app_config(request: Request):
18421846
**(
18431847
{
18441848
"enable_direct_connections": app.state.config.ENABLE_DIRECT_CONNECTIONS,
1849+
"enable_folders": app.state.config.ENABLE_FOLDERS,
18451850
"enable_channels": app.state.config.ENABLE_CHANNELS,
18461851
"enable_notes": app.state.config.ENABLE_NOTES,
18471852
"enable_web_search": app.state.config.ENABLE_WEB_SEARCH,
@@ -2014,7 +2019,10 @@ async def get_current_usage(user=Depends(get_verified_user)):
20142019
This is an experimental endpoint and subject to change.
20152020
"""
20162021
try:
2017-
return {"model_ids": get_models_in_use(), "user_ids": get_active_user_ids()}
2022+
return {
2023+
"model_ids": get_models_in_use(),
2024+
"user_count": Users.get_active_user_count(),
2025+
}
20182026
except Exception as e:
20192027
log.error(f"Error getting usage statistics: {e}")
20202028
raise HTTPException(status_code=500, detail="Internal Server Error")
@@ -2077,7 +2085,7 @@ async def get_current_usage(user=Depends(get_verified_user)):
20772085
)
20782086

20792087

2080-
async def register_client(self, request, client_id: str) -> bool:
2088+
async def register_client(request, client_id: str) -> bool:
20812089
server_type, server_id = client_id.split(":", 1)
20822090

20832091
connection = None
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""Update messages and channel member table
2+
3+
Revision ID: 2f1211949ecc
4+
Revises: 37f288994c47
5+
Create Date: 2025-11-27 03:07:56.200231
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
import open_webui.internal.db
14+
15+
16+
# revision identifiers, used by Alembic.
17+
revision: str = "2f1211949ecc"
18+
down_revision: Union[str, None] = "37f288994c47"
19+
branch_labels: Union[str, Sequence[str], None] = None
20+
depends_on: Union[str, Sequence[str], None] = None
21+
22+
23+
def upgrade() -> None:
24+
# New columns to be added to channel_member table
25+
op.add_column("channel_member", sa.Column("status", sa.Text(), nullable=True))
26+
op.add_column(
27+
"channel_member",
28+
sa.Column(
29+
"is_active",
30+
sa.Boolean(),
31+
nullable=False,
32+
default=True,
33+
server_default=sa.sql.expression.true(),
34+
),
35+
)
36+
37+
op.add_column(
38+
"channel_member",
39+
sa.Column(
40+
"is_channel_muted",
41+
sa.Boolean(),
42+
nullable=False,
43+
default=False,
44+
server_default=sa.sql.expression.false(),
45+
),
46+
)
47+
op.add_column(
48+
"channel_member",
49+
sa.Column(
50+
"is_channel_pinned",
51+
sa.Boolean(),
52+
nullable=False,
53+
default=False,
54+
server_default=sa.sql.expression.false(),
55+
),
56+
)
57+
58+
op.add_column("channel_member", sa.Column("data", sa.JSON(), nullable=True))
59+
op.add_column("channel_member", sa.Column("meta", sa.JSON(), nullable=True))
60+
61+
op.add_column(
62+
"channel_member", sa.Column("joined_at", sa.BigInteger(), nullable=False)
63+
)
64+
op.add_column(
65+
"channel_member", sa.Column("left_at", sa.BigInteger(), nullable=True)
66+
)
67+
68+
op.add_column(
69+
"channel_member", sa.Column("last_read_at", sa.BigInteger(), nullable=True)
70+
)
71+
72+
op.add_column(
73+
"channel_member", sa.Column("updated_at", sa.BigInteger(), nullable=True)
74+
)
75+
76+
# New columns to be added to message table
77+
op.add_column(
78+
"message",
79+
sa.Column(
80+
"is_pinned",
81+
sa.Boolean(),
82+
nullable=False,
83+
default=False,
84+
server_default=sa.sql.expression.false(),
85+
),
86+
)
87+
op.add_column("message", sa.Column("pinned_at", sa.BigInteger(), nullable=True))
88+
op.add_column("message", sa.Column("pinned_by", sa.Text(), nullable=True))
89+
90+
91+
def downgrade() -> None:
92+
op.drop_column("channel_member", "updated_at")
93+
op.drop_column("channel_member", "last_read_at")
94+
95+
op.drop_column("channel_member", "meta")
96+
op.drop_column("channel_member", "data")
97+
98+
op.drop_column("channel_member", "is_channel_pinned")
99+
op.drop_column("channel_member", "is_channel_muted")
100+
101+
op.drop_column("message", "pinned_by")
102+
op.drop_column("message", "pinned_at")
103+
op.drop_column("message", "is_pinned")

backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,46 @@
2020

2121

2222
def upgrade() -> None:
23+
# Ensure 'id' column in 'user' table is unique and primary key (ForeignKey constraint)
24+
inspector = sa.inspect(op.get_bind())
25+
columns = inspector.get_columns("user")
26+
27+
pk_columns = inspector.get_pk_constraint("user")["constrained_columns"]
28+
id_column = next((col for col in columns if col["name"] == "id"), None)
29+
30+
if id_column and not id_column.get("unique", False):
31+
unique_constraints = inspector.get_unique_constraints("user")
32+
unique_columns = {tuple(u["column_names"]) for u in unique_constraints}
33+
34+
with op.batch_alter_table("user") as batch_op:
35+
# If primary key is wrong, drop it
36+
if pk_columns and pk_columns != ["id"]:
37+
batch_op.drop_constraint(
38+
inspector.get_pk_constraint("user")["name"], type_="primary"
39+
)
40+
41+
# Add unique constraint if missing
42+
if ("id",) not in unique_columns:
43+
batch_op.create_unique_constraint("uq_user_id", ["id"])
44+
45+
# Re-create correct primary key
46+
batch_op.create_primary_key("pk_user_id", ["id"])
47+
2348
# Create oauth_session table
2449
op.create_table(
2550
"oauth_session",
26-
sa.Column("id", sa.Text(), nullable=False),
27-
sa.Column("user_id", sa.Text(), nullable=False),
51+
sa.Column("id", sa.Text(), primary_key=True, nullable=False, unique=True),
52+
sa.Column(
53+
"user_id",
54+
sa.Text(),
55+
sa.ForeignKey("user.id", ondelete="CASCADE"),
56+
nullable=False,
57+
),
2858
sa.Column("provider", sa.Text(), nullable=False),
2959
sa.Column("token", sa.Text(), nullable=False),
3060
sa.Column("expires_at", sa.BigInteger(), nullable=False),
3161
sa.Column("created_at", sa.BigInteger(), nullable=False),
3262
sa.Column("updated_at", sa.BigInteger(), nullable=False),
33-
sa.PrimaryKeyConstraint("id"),
34-
sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"),
3563
)
3664

3765
# Create indexes for better performance

0 commit comments

Comments
 (0)