From 17dbc6f001aeea25ae1df528cb79bb272eca4a77 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 9 Sep 2026 17:09:53 -0400 Subject: [PATCH] refac --- backend/open_webui/models/knowledge.py | 20 +++++++++++++++ backend/open_webui/routers/knowledge.py | 33 +++++++++++++++++++------ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/backend/open_webui/models/knowledge.py b/backend/open_webui/models/knowledge.py index 848232d6f2..9361da9ad9 100644 --- a/backend/open_webui/models/knowledge.py +++ b/backend/open_webui/models/knowledge.py @@ -1069,6 +1069,26 @@ class KnowledgeTable: for child_id in child_ids: await self._delete_files_in_subtree(child_id, db=db) + async def get_files_by_id_and_directory_id( + self, + knowledge_id: str, + directory_id: str, + db: Optional[AsyncSession] = None, + ) -> list[FileModel]: + """Get all files in a directory and its subdirectories.""" + async with get_async_db_context(db) as db: + directory_ids = [directory_id] + for parent_id in directory_ids: + result = await db.execute(select(KnowledgeDirectory.id).filter_by(parent_id=parent_id)) + directory_ids.extend(result.scalars().all()) + result = await db.execute( + select(File) + .join(KnowledgeFile, File.id == KnowledgeFile.file_id) + .filter(KnowledgeFile.knowledge_id == knowledge_id) + .filter(KnowledgeFile.directory_id.in_(directory_ids)) + ) + return [FileModel.model_validate(file) for file in result.scalars().all()] + async def move_file_to_directory( self, knowledge_id: str, diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index 3b58c6f805..43ec07a53c 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -1642,13 +1642,9 @@ async def remove_file_from_knowledge_by_id( # Remove content from the vector database try: - await ASYNC_VECTOR_DB_CLIENT.delete( - collection_name=knowledge.id, filter={'file_id': form_data.file_id} - ) # Remove by file_id first - - await ASYNC_VECTOR_DB_CLIENT.delete( - collection_name=knowledge.id, filter={'hash': file.hash} - ) # Remove by hash as well in case of duplicates + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': form_data.file_id}) + if file.hash: + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'hash': file.hash}) except Exception as e: log.debug('This was most likely caused by bypassing embedding processing') log.debug(e) @@ -1988,7 +1984,8 @@ async def sync_knowledge_cleanup( try: await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=id, filter={'file_id': file_id}) - await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=id, filter={'hash': file.hash}) + if file.hash: + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=id, filter={'hash': file.hash}) except Exception: pass @@ -2321,6 +2318,9 @@ async def delete_knowledge_directory( detail=ERROR_MESSAGES.NOT_FOUND, ) + # Collect before delete_directory drops the KnowledgeFile rows + files = [] if move_files else await Knowledges.get_files_by_id_and_directory_id(id, dir_id, db=db) + success = await Knowledges.delete_directory( directory_id=dir_id, move_files_to_parent=move_files, @@ -2331,6 +2331,23 @@ async def delete_knowledge_directory( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Failed to delete directory.', ) + + for file in files: + try: + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=id, filter={'file_id': file.id}) + if file.hash: + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=id, filter={'hash': file.hash}) + except Exception as e: + log.debug('This was most likely caused by bypassing embedding processing') + log.debug(e) + + if ( + not ENABLE_KNOWLEDGE_FILE_RETENTION + and not await Knowledges.get_knowledges_by_file_id(file.id, db=db) + and (file.user_id == user.id or user.role == 'admin') + ): + await delete_file_resource(file, db) + await publish_event( request, EVENTS.KNOWLEDGE_DIRECTORY_DELETED,