From a22aaecd4590470fb12d2d65f20e3e97753e8538 Mon Sep 17 00:00:00 2001 From: DrMelone <27028174+Classic298@users.noreply.github.com> Date: Wed, 28 Jan 2026 22:15:01 +0100 Subject: [PATCH] fix: add separate original_path field for directory sync fix: add separate original_path field for directory sync Previously, meta.name was overloaded to store the full path for directory sync comparison (e.g., "docs/subfolder/readme.md"). This caused potential downstream effects since meta.name is used for display and downloads. Changes: - Revert meta.name to store only the sanitized base filename - Add new meta.original_path field that preserves the full upload path - Update sync compare logic to prioritize original_path for matching - Fallback chain for legacy files: original_path -> name -> filename This maintains backwards compatibility with existing files while enabling directory structure preservation for the sync feature. --- backend/open_webui/routers/files.py | 5 +++-- backend/open_webui/routers/knowledge.py | 20 ++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index facf032fc5..7900baa495 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -260,11 +260,12 @@ async def upload_file_handler( **({'status': 'pending'} if process else {}), }, 'meta': { - # Store original filename with path for sync comparison - 'name': unsanitized_filename, + 'name': name, 'content_type': (file.content_type if isinstance(file.content_type, str) else None), 'size': len(contents), 'file_hash': file_hash, + # Store original path for directory sync (includes subdirectories) + 'original_path': unsanitized_filename, 'data': file_metadata, }, } diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index b648a8824e..977a774261 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -1167,12 +1167,16 @@ async def compare_files_for_sync( # Get all files currently in the knowledge base existing_files = await Knowledges.get_files_by_id(id, db=db) - # Build a map of existing files by filename for quick lookup - existing_by_filename: dict[str, FileModel] = {} + # Build a map of existing files by their sync path for quick lookup + # Priority: original_path (for directory sync) > name > filename + existing_by_path: dict[str, FileModel] = {} for file in existing_files: - # Use the original filename from meta if available, otherwise use filename field - filename = file.meta.get('name', file.filename) if file.meta else file.filename - existing_by_filename[filename] = file + if file.meta: + # Use original_path for sync comparison (includes subdirectory structure) + sync_path = file.meta.get('original_path') or file.meta.get('name', file.filename) + else: + sync_path = file.filename + existing_by_path[sync_path] = file # Track files from the incoming directory incoming_filenames = set() @@ -1185,7 +1189,7 @@ async def compare_files_for_sync( incoming_filenames.add(incoming_file.file_path) # Check if file exists in knowledge base - existing_file = existing_by_filename.get(incoming_file.file_path) + existing_file = existing_by_path.get(incoming_file.file_path) if existing_file: # Check if hash is already stored in meta (files uploaded after this feature) @@ -1228,8 +1232,8 @@ async def compare_files_for_sync( new_files.append(incoming_file.file_path) # Find files to delete (exist in KB but not in incoming directory) - for filename, file in existing_by_filename.items(): - if filename not in incoming_filenames: + for sync_path, file in existing_by_path.items(): + if sync_path not in incoming_filenames: removed_file_ids.append(file.id)