This commit is contained in:
Timothy Jaeryang Baek committed 2026-06-25 03:31:45 +01:00
1 parent 35f8bf97e3
commit b5c43968db
32 files changed
+2251 -120

No files matched your search

+166 -8
View File
@@ -10,6 +10,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import StreamingResponse
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from open_webui.events import EVENTS, publish_event
from open_webui.internal.db import get_async_session
from open_webui.models.access_grants import AccessGrants
from open_webui.models.config import Config
@@ -544,6 +545,13 @@ async def delete_all_user_chats(
)
result = await Chats.delete_chats_by_user_id(user.id, db=db)
if result:
await publish_event(
request,
EVENTS.CHAT_DELETED_ALL,
actor=user,
subject_id=user.id, subject_type='user',
)
return result
@@ -590,6 +598,7 @@ async def get_user_chat_list_by_user_id(
@router.post('/new', response_model=ChatResponse | None)
async def create_new_chat(
request: Request,
form_data: ChatForm,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
@@ -611,6 +620,13 @@ async def create_new_chat(
try:
chat = await Chats.insert_new_chat(str(uuid4()), user.id, form_data, db=db)
await publish_event(
request,
EVENTS.CHAT_CREATED,
actor=user,
subject_id=chat.id,
data={'title': chat.title, 'folder_id': chat.folder_id},
)
return ChatResponse(**chat.model_dump())
except Exception as e:
log.exception(e)
@@ -633,6 +649,13 @@ async def import_chats(
try:
chats = await Chats.import_chats(user.id, form_data.chats, db=db)
await publish_event(
request,
EVENTS.CHAT_IMPORTED,
actor=user,
subject_type='chat.import',
data={'count': len(chats), 'chat_ids': [chat.id for chat in chats]},
)
return chats
except Exception as e:
log.exception(e)
@@ -889,8 +912,13 @@ async def get_archived_session_user_chat_count(
@router.post('/archive/all', response_model=bool)
async def archive_all_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
return await Chats.archive_all_chats_by_user_id(user.id, db=db)
async def archive_all_chats(
request: Request, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
result = await Chats.archive_all_chats_by_user_id(user.id, db=db)
if result:
await publish_event(request, EVENTS.CHAT_ARCHIVED, actor=user, subject_id=user.id, subject_type='user')
return result
############################
@@ -899,8 +927,13 @@ async def archive_all_chats(user=Depends(get_verified_user), db: AsyncSession =
@router.post('/unarchive/all', response_model=bool)
async def unarchive_all_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
return await Chats.unarchive_all_chats_by_user_id(user.id, db=db)
async def unarchive_all_chats(
request: Request, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
result = await Chats.unarchive_all_chats_by_user_id(user.id, db=db)
if result:
await publish_event(request, EVENTS.CHAT_UNARCHIVED, actor=user, subject_id=user.id, subject_type='user')
return result
############################
@@ -909,7 +942,9 @@ async def unarchive_all_chats(user=Depends(get_verified_user), db: AsyncSession
@router.delete('/share/all', response_model=bool)
async def unshare_all_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
async def unshare_all_chats(
request: Request, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
# Collect chat_ids that have shares so we can clear share_id and access grants
shared_list = await SharedChats.get_by_user_id(user.id, db=db)
chat_ids = [s.chat_id for s in shared_list]
@@ -922,6 +957,14 @@ async def unshare_all_chats(user=Depends(get_verified_user), db: AsyncSession =
await Chats.update_chat_share_id_by_id(chat_id, None, db=db)
await AccessGrants.set_access_grants('shared_chat', chat_id, [], db=db)
if result:
await publish_event(
request,
EVENTS.CHAT_UNSHARED,
actor=user,
subject_id=user.id, subject_type='user',
data={'count': len(chat_ids), 'chat_ids': chat_ids},
)
return result
@@ -1067,7 +1110,16 @@ async def compact_chat_by_id(
if not model_id:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='No model found for context compaction.')
return await compact_chat_branch(request, user, chat, model_id, request.app.state.MODELS)
result = await compact_chat_branch(request, user, chat, model_id, request.app.state.MODELS)
if result.get('compacted'):
await publish_event(
request,
EVENTS.CHAT_COMPACTED,
actor=user,
subject_id=id,
data={'dropped_messages': result.get('dropped_messages')},
)
return result
############################
@@ -1115,6 +1167,7 @@ async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSess
@router.post('/{id}', response_model=ChatResponse | None)
async def update_chat_by_id(
request: Request,
id: str,
form_data: ChatForm,
user=Depends(get_verified_user),
@@ -1143,6 +1196,13 @@ async def update_chat_by_id(
if messages:
await Chats.reconcile_messages_by_chat_id(id, user.id, messages)
await publish_event(
request,
EVENTS.CHAT_UPDATED,
actor=user,
subject_id=id,
data={'title': chat.title},
)
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
@@ -1160,6 +1220,7 @@ class MessageForm(BaseModel):
@router.post('/{id}/messages/{message_id}', response_model=ChatResponse | None)
async def update_chat_message_by_id(
request: Request,
id: str,
message_id: str,
form_data: MessageForm,
@@ -1209,6 +1270,13 @@ async def update_chat_message_by_id(
}
)
await publish_event(
request,
EVENTS.MESSAGE_UPDATED,
actor=user,
subject_id=message_id,
data={'chat_id': id, 'content_preview': form_data.content[:300]},
)
return ChatResponse(**chat.model_dump())
@@ -1222,6 +1290,7 @@ class EventForm(BaseModel):
@router.post('/{id}/messages/{message_id}/event', response_model=bool | None)
async def send_chat_message_event_by_id(
request: Request,
id: str,
message_id: str,
form_data: EventForm,
@@ -1255,6 +1324,13 @@ async def send_chat_message_event_by_id(
await event_emitter(form_data.model_dump())
else:
return False
await publish_event(
request,
EVENTS.MESSAGE_EVENT_RECEIVED,
actor=user,
subject_id=message_id,
data={'chat_id': id, 'event_type': form_data.type},
)
return True
except Exception:
return False
@@ -1287,6 +1363,14 @@ async def delete_chat_by_id(
result = await Chats.delete_chat_by_id(id, db=db)
if result:
await publish_event(
request,
EVENTS.CHAT_DELETED,
actor=user,
subject_id=id,
data={'owner_id': chat.user_id},
)
return result
else:
if not await has_permission(user.id, 'chat.delete', await Config.get('user.permissions')):
@@ -1304,6 +1388,14 @@ async def delete_chat_by_id(
await Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
result = await Chats.delete_chat_by_id_and_user_id(id, user.id, db=db)
if result:
await publish_event(
request,
EVENTS.CHAT_DELETED,
actor=user,
subject_id=id,
data={'owner_id': user.id},
)
return result
@@ -1329,10 +1421,18 @@ async def get_pinned_status_by_id(
@router.post('/{id}/pin', response_model=ChatResponse | None)
async def pin_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
async def pin_chat_by_id(
request: Request, id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
chat = await Chats.toggle_chat_pinned_by_id(id, db=db)
await publish_event(
request,
EVENTS.CHAT_PINNED if chat.pinned else EVENTS.CHAT_UNPINNED,
actor=user,
subject_id=id, subject_type='chat',
)
return chat
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
@@ -1383,6 +1483,13 @@ async def clone_chat_by_id(
if chats:
chat = chats[0]
await publish_event(
request,
EVENTS.CHAT_CLONED,
actor=user,
subject_id=chat.id,
data={'original_chat_id': id},
)
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
@@ -1493,6 +1600,12 @@ async def archive_chat_by_id(
# Unarchived — ensure tag rows exist
await Tags.ensure_tags_exist(tag_ids, user.id, db=db)
await publish_event(
request,
EVENTS.CHAT_ARCHIVED if chat.archived else EVENTS.CHAT_UNARCHIVED,
actor=user,
subject_id=id, subject_type='chat',
)
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
@@ -1522,6 +1635,13 @@ async def share_chat_by_id(
shared = await SharedChats.update(chat.share_id, db=db)
if shared:
chat = await Chats.get_chat_by_id(id, db=db)
await publish_event(
request,
EVENTS.CHAT_SHARED,
actor=user,
subject_id=id,
data={'share_id': chat.share_id, 'updated': True},
)
return ChatResponse(**chat.model_dump())
# Create a new share
@@ -1533,6 +1653,13 @@ async def share_chat_by_id(
if not chat:
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=ERROR_MESSAGES.DEFAULT())
await publish_event(
request,
EVENTS.CHAT_SHARED,
actor=user,
subject_id=id,
data={'share_id': shared.id},
)
return ChatResponse(**chat.model_dump())
@@ -1541,7 +1668,7 @@ async def share_chat_by_id(
@router.delete('/{id}/share', response_model=bool | None)
async def delete_shared_chat_by_id(
id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
request: Request, id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if not chat:
@@ -1554,6 +1681,13 @@ async def delete_shared_chat_by_id(
await Chats.update_chat_share_id_by_id(id, None, db=db)
await AccessGrants.set_access_grants('shared_chat', id, [], db=db)
await publish_event(
request,
EVENTS.CHAT_UNSHARED,
actor=user,
subject_id=id,
data={'share_id': chat.share_id},
)
return True
@@ -1641,6 +1775,7 @@ class ChatFolderIdForm(BaseModel):
@router.post('/{id}/folder', response_model=ChatResponse | None)
async def update_chat_folder_id_by_id(
request: Request,
id: str,
form_data: ChatFolderIdForm,
user=Depends(get_verified_user),
@@ -1661,6 +1796,13 @@ async def update_chat_folder_id_by_id(
)
chat = await Chats.update_chat_folder_id_by_id_and_user_id(id, user.id, form_data.folder_id, db=db)
await publish_event(
request,
EVENTS.CHAT_FOLDER_UPDATED,
actor=user,
subject_id=id,
data={'folder_id': form_data.folder_id},
)
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
@@ -1688,6 +1830,7 @@ async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user), db: Asyn
@router.post('/{id}/tags', response_model=list[TagModel])
async def add_tag_by_id_and_tag_name(
request: Request,
id: str,
form_data: TagForm,
user=Depends(get_verified_user),
@@ -1706,6 +1849,13 @@ async def add_tag_by_id_and_tag_name(
if tag_id not in tags:
await Chats.add_chat_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
await publish_event(
request,
EVENTS.CHAT_TAG_ADDED,
actor=user,
subject_id=id,
data={'tag': form_data.name},
)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
tags = chat.meta.get('tags', [])
@@ -1721,6 +1871,7 @@ async def add_tag_by_id_and_tag_name(
@router.delete('/{id}/tags', response_model=list[TagModel])
async def delete_tag_by_id_and_tag_name(
request: Request,
id: str,
form_data: TagForm,
user=Depends(get_verified_user),
@@ -1729,6 +1880,13 @@ async def delete_tag_by_id_and_tag_name(
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
await Chats.delete_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
await publish_event(
request,
EVENTS.CHAT_TAG_REMOVED,
actor=user,
subject_id=id,
data={'tag': form_data.name},
)
if await Chats.count_chats_by_tag_name_and_user_id(form_data.name, user.id, db=db) == 0:
await Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)