Skip to content

Commit 80abf36

Browse files
Merge remote-tracking branch 'upstream/dev' into dev
2 parents 65cd9b1 + aac9812 commit 80abf36

32 files changed

Lines changed: 1537 additions & 590 deletions

backend/open_webui/env.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,22 @@ def parse_section(section):
392392
REDIS_SOCKET_CONNECT_TIMEOUT = float(REDIS_SOCKET_CONNECT_TIMEOUT)
393393
except ValueError:
394394
REDIS_SOCKET_CONNECT_TIMEOUT = None
395+
396+
REDIS_RECONNECT_DELAY = os.environ.get(
397+
"REDIS_RECONNECT_DELAY", ""
398+
)
399+
400+
if REDIS_RECONNECT_DELAY == "":
401+
REDIS_RECONNECT_DELAY = None
402+
else:
403+
try:
404+
REDIS_RECONNECT_DELAY = float(
405+
REDIS_RECONNECT_DELAY
406+
)
407+
if REDIS_RECONNECT_DELAY < 0:
408+
REDIS_RECONNECT_DELAY = None
409+
except Exception:
410+
REDIS_RECONNECT_DELAY = None
395411

396412
####################################
397413
# UVICORN WORKERS

backend/open_webui/models/chats.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ class ChatTitleIdResponse(BaseModel):
168168
created_at: int
169169

170170

171+
class SharedChatResponse(BaseModel):
172+
id: str
173+
title: str
174+
share_id: Optional[str] = None
175+
updated_at: int
176+
created_at: int
177+
178+
171179
class ChatListResponse(BaseModel):
172180
items: list[ChatModel]
173181
total: int
@@ -675,6 +683,49 @@ def get_archived_chat_list_by_user_id(
675683
all_chats = query.all()
676684
return [ChatModel.model_validate(chat) for chat in all_chats]
677685

686+
def get_shared_chat_list_by_user_id(
687+
self,
688+
user_id: str,
689+
filter: Optional[dict] = None,
690+
skip: int = 0,
691+
limit: int = 50,
692+
db: Optional[Session] = None,
693+
) -> list[ChatModel]:
694+
695+
with get_db_context(db) as db:
696+
query = db.query(Chat).filter_by(user_id=user_id).filter(
697+
Chat.share_id.isnot(None)
698+
)
699+
700+
if filter:
701+
query_key = filter.get("query")
702+
if query_key:
703+
query = query.filter(Chat.title.ilike(f"%{query_key}%"))
704+
705+
order_by = filter.get("order_by")
706+
direction = filter.get("direction")
707+
708+
if order_by and direction:
709+
if not getattr(Chat, order_by, None):
710+
raise ValueError("Invalid order_by field")
711+
712+
if direction.lower() == "asc":
713+
query = query.order_by(getattr(Chat, order_by).asc())
714+
elif direction.lower() == "desc":
715+
query = query.order_by(getattr(Chat, order_by).desc())
716+
else:
717+
raise ValueError("Invalid direction for ordering")
718+
else:
719+
query = query.order_by(Chat.updated_at.desc())
720+
721+
if skip:
722+
query = query.offset(skip)
723+
if limit:
724+
query = query.limit(limit)
725+
726+
all_chats = query.all()
727+
return [ChatModel.model_validate(chat) for chat in all_chats]
728+
678729
def get_chat_list_by_user_id(
679730
self,
680731
user_id: str,

backend/open_webui/models/feedbacks.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -460,23 +460,15 @@ def delete_feedbacks_by_user_id(
460460
self, user_id: str, db: Optional[Session] = None
461461
) -> bool:
462462
with get_db_context(db) as db:
463-
feedbacks = db.query(Feedback).filter_by(user_id=user_id).all()
464-
if not feedbacks:
465-
return False
466-
for feedback in feedbacks:
467-
db.delete(feedback)
463+
result = db.query(Feedback).filter_by(user_id=user_id).delete()
468464
db.commit()
469-
return True
465+
return result > 0
470466

471467
def delete_all_feedbacks(self, db: Optional[Session] = None) -> bool:
472468
with get_db_context(db) as db:
473-
feedbacks = db.query(Feedback).all()
474-
if not feedbacks:
475-
return False
476-
for feedback in feedbacks:
477-
db.delete(feedback)
469+
result = db.query(Feedback).delete()
478470
db.commit()
479-
return True
471+
return result > 0
480472

481473

482474
Feedbacks = FeedbackTable()

backend/open_webui/models/files.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from sqlalchemy.orm import Session
66
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
7-
from pydantic import BaseModel, ConfigDict
7+
from pydantic import BaseModel, ConfigDict, model_validator
88
from sqlalchemy import BigInteger, Column, String, Text, JSON
99

1010
log = logging.getLogger(__name__)
@@ -63,6 +63,25 @@ class FileMeta(BaseModel):
6363

6464
model_config = ConfigDict(extra="allow")
6565

66+
@model_validator(mode="before")
67+
@classmethod
68+
def sanitize_meta(cls, data):
69+
"""Sanitize metadata fields to handle malformed legacy data."""
70+
if not isinstance(data, dict):
71+
return data
72+
73+
# Handle content_type that may be a list like ['application/pdf', None]
74+
content_type = data.get("content_type")
75+
if isinstance(content_type, list):
76+
# Extract first non-None string value
77+
data["content_type"] = next(
78+
(item for item in content_type if isinstance(item, str)), None
79+
)
80+
elif content_type is not None and not isinstance(content_type, str):
81+
data["content_type"] = None
82+
83+
return data
84+
6685

6786
class FileModelResponse(BaseModel):
6887
id: str
@@ -74,7 +93,7 @@ class FileModelResponse(BaseModel):
7493
meta: FileMeta
7594

7695
created_at: int # timestamp in epoch
77-
updated_at: int # timestamp in epoch
96+
updated_at: Optional[int] = None # timestamp in epoch, optional for legacy files
7897

7998
model_config = ConfigDict(extra="allow")
8099

backend/open_webui/models/functions.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,26 @@ def get_function_by_id(
195195
except Exception:
196196
return None
197197

198+
def get_functions_by_ids(
199+
self, ids: list[str], db: Optional[Session] = None
200+
) -> list[FunctionModel]:
201+
"""
202+
Batch fetch multiple functions by their IDs in a single query.
203+
Returns functions in the same order as the input IDs (None entries filtered out).
204+
"""
205+
if not ids:
206+
return []
207+
try:
208+
with get_db_context(db) as db:
209+
functions = db.query(Function).filter(Function.id.in_(ids)).all()
210+
# Create a dict for O(1) lookup
211+
func_dict = {f.id: FunctionModel.model_validate(f) for f in functions}
212+
# Return in original order, filtering out any not found
213+
return [func_dict[id] for id in ids if id in func_dict]
214+
except Exception:
215+
return []
216+
217+
198218
def get_functions(
199219
self, active_only=False, include_valves=False, db: Optional[Session] = None
200220
) -> list[FunctionModel | FunctionWithValvesModel]:
@@ -299,7 +319,7 @@ def update_function_valves_by_id(
299319
function.updated_at = int(time.time())
300320
db.commit()
301321
db.refresh(function)
302-
return self.get_function_by_id(id, db=db)
322+
return FunctionModel.model_validate(function)
303323
except Exception:
304324
return None
305325

@@ -319,7 +339,7 @@ def update_function_metadata_by_id(
319339
function.updated_at = int(time.time())
320340
db.commit()
321341
db.refresh(function)
322-
return self.get_function_by_id(id, db=db)
342+
return FunctionModel.model_validate(function)
323343
else:
324344
return None
325345
except Exception as e:
@@ -381,7 +401,8 @@ def update_function_by_id(
381401
}
382402
)
383403
db.commit()
384-
return self.get_function_by_id(id, db=db)
404+
function = db.get(Function, id)
405+
return FunctionModel.model_validate(function) if function else None
385406
except Exception:
386407
return None
387408

backend/open_webui/models/groups.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -589,11 +589,10 @@ def remove_users_from_group(
589589
if not user_ids:
590590
return GroupModel.model_validate(group)
591591

592-
# Remove each user from group_member
593-
for user_id in user_ids:
594-
db.query(GroupMember).filter(
595-
GroupMember.group_id == id, GroupMember.user_id == user_id
596-
).delete()
592+
# Remove users from group_member in batch
593+
db.query(GroupMember).filter(
594+
GroupMember.group_id == id, GroupMember.user_id.in_(user_ids)
595+
).delete(synchronize_session=False)
597596

598597
# Update group timestamp
599598
group.updated_at = int(time.time())

backend/open_webui/models/memories.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ def update_memory_by_id_and_user_id(
8282
memory.updated_at = int(time.time())
8383

8484
db.commit()
85-
return self.get_memory_by_id(id)
85+
db.refresh(memory)
86+
return MemoryModel.model_validate(memory)
8687
except Exception:
8788
return None
8889

backend/open_webui/models/models.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -394,17 +394,16 @@ def toggle_model_by_id(
394394
) -> Optional[ModelModel]:
395395
with get_db_context(db) as db:
396396
try:
397-
is_active = db.query(Model).filter_by(id=id).first().is_active
397+
model = db.query(Model).filter_by(id=id).first()
398+
if not model:
399+
return None
398400

399-
db.query(Model).filter_by(id=id).update(
400-
{
401-
"is_active": not is_active,
402-
"updated_at": int(time.time()),
403-
}
404-
)
401+
model.is_active = not model.is_active
402+
model.updated_at = int(time.time())
405403
db.commit()
404+
db.refresh(model)
406405

407-
return self.get_model_by_id(id, db=db)
406+
return ModelModel.model_validate(model)
408407
except Exception:
409408
return None
410409

backend/open_webui/models/users.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -530,9 +530,12 @@ def update_user_role_by_id(
530530
) -> Optional[UserModel]:
531531
try:
532532
with get_db_context(db) as db:
533-
db.query(User).filter_by(id=id).update({"role": role})
534-
db.commit()
535533
user = db.query(User).filter_by(id=id).first()
534+
if not user:
535+
return None
536+
user.role = role
537+
db.commit()
538+
db.refresh(user)
536539
return UserModel.model_validate(user)
537540
except Exception:
538541
return None
@@ -542,12 +545,13 @@ def update_user_status_by_id(
542545
) -> Optional[UserModel]:
543546
try:
544547
with get_db_context(db) as db:
545-
db.query(User).filter_by(id=id).update(
546-
{**form_data.model_dump(exclude_none=True)}
547-
)
548-
db.commit()
549-
550548
user = db.query(User).filter_by(id=id).first()
549+
if not user:
550+
return None
551+
for key, value in form_data.model_dump(exclude_none=True).items():
552+
setattr(user, key, value)
553+
db.commit()
554+
db.refresh(user)
551555
return UserModel.model_validate(user)
552556
except Exception:
553557
return None
@@ -557,12 +561,12 @@ def update_user_profile_image_url_by_id(
557561
) -> Optional[UserModel]:
558562
try:
559563
with get_db_context(db) as db:
560-
db.query(User).filter_by(id=id).update(
561-
{"profile_image_url": profile_image_url}
562-
)
563-
db.commit()
564-
565564
user = db.query(User).filter_by(id=id).first()
565+
if not user:
566+
return None
567+
user.profile_image_url = profile_image_url
568+
db.commit()
569+
db.refresh(user)
566570
return UserModel.model_validate(user)
567571
except Exception:
568572
return None
@@ -573,12 +577,12 @@ def update_last_active_by_id(
573577
) -> Optional[UserModel]:
574578
try:
575579
with get_db_context(db) as db:
576-
db.query(User).filter_by(id=id).update(
577-
{"last_active_at": int(time.time())}
578-
)
579-
db.commit()
580-
581580
user = db.query(User).filter_by(id=id).first()
581+
if not user:
582+
return None
583+
user.last_active_at = int(time.time())
584+
db.commit()
585+
db.refresh(user)
582586
return UserModel.model_validate(user)
583587
except Exception:
584588
return None
@@ -620,12 +624,14 @@ def update_user_by_id(
620624
) -> Optional[UserModel]:
621625
try:
622626
with get_db_context(db) as db:
623-
db.query(User).filter_by(id=id).update(updated)
624-
db.commit()
625-
626627
user = db.query(User).filter_by(id=id).first()
628+
if not user:
629+
return None
630+
for key, value in updated.items():
631+
setattr(user, key, value)
632+
db.commit()
633+
db.refresh(user)
627634
return UserModel.model_validate(user)
628-
# return UserModel(**user.dict())
629635
except Exception as e:
630636
print(e)
631637
return None

0 commit comments

Comments
 (0)