From bdcd55a871cbefaafcf8642d42aaa2f5249b70e4 Mon Sep 17 00:00:00 2001 From: yangdx Date: Wed, 25 Jun 2025 19:02:46 +0800 Subject: [PATCH] Feat: Add delete upload file option to document deletion --- lightrag/api/routers/document_routes.py | 61 ++++++++++++++++++- lightrag/base.py | 1 + lightrag/lightrag.py | 5 ++ lightrag_webui/src/api/lightrag.ts | 4 +- .../documents/DeleteDocumentsDialog.tsx | 20 +++++- lightrag_webui/src/locales/ar.json | 4 +- lightrag_webui/src/locales/en.json | 4 +- lightrag_webui/src/locales/fr.json | 4 +- lightrag_webui/src/locales/zh.json | 4 +- lightrag_webui/src/locales/zh_TW.json | 4 +- 10 files changed, 99 insertions(+), 12 deletions(-) diff --git a/lightrag/api/routers/document_routes.py b/lightrag/api/routers/document_routes.py index fe72b57c..3c518dca 100644 --- a/lightrag/api/routers/document_routes.py +++ b/lightrag/api/routers/document_routes.py @@ -261,6 +261,10 @@ Attributes: class DeleteDocRequest(BaseModel): doc_ids: List[str] = Field(..., description="The IDs of the documents to delete.") + delete_file: bool = Field( + default=False, + description="Whether to delete the corresponding file in the upload directory.", + ) @field_validator("doc_ids", mode="after") @classmethod @@ -793,7 +797,12 @@ async def run_scanning_process(rag: LightRAG, doc_manager: DocumentManager): logger.error(traceback.format_exc()) -async def background_delete_documents(rag: LightRAG, doc_ids: List[str]): +async def background_delete_documents( + rag: LightRAG, + doc_manager: DocumentManager, + doc_ids: List[str], + delete_file: bool = False, +): """Background task to delete multiple documents""" from lightrag.kg.shared_storage import ( get_namespace_data, @@ -847,6 +856,46 @@ async def background_delete_documents(rag: LightRAG, doc_ids: List[str]): async with pipeline_status_lock: pipeline_status["history_messages"].append(success_msg) + + # Handle file deletion if requested and file_path is available + if ( + delete_file + and result.file_path + and result.file_path != "unknown_source" + ): + try: + file_path = doc_manager.input_dir / result.file_path + if file_path.exists(): + file_path.unlink() + file_delete_msg = ( + f"Successfully deleted file: {result.file_path}" + ) + logger.info(file_delete_msg) + async with pipeline_status_lock: + pipeline_status["history_messages"].append( + file_delete_msg + ) + else: + file_not_found_msg = ( + f"File not found for deletion: {result.file_path}" + ) + logger.warning(file_not_found_msg) + async with pipeline_status_lock: + pipeline_status["history_messages"].append( + file_not_found_msg + ) + except Exception as file_error: + file_error_msg = f"Failed to delete file {result.file_path}: {str(file_error)}" + logger.error(file_error_msg) + async with pipeline_status_lock: + pipeline_status["history_messages"].append( + file_error_msg + ) + elif delete_file: + no_file_msg = f"No valid file path found for document {doc_id}" + logger.warning(no_file_msg) + async with pipeline_status_lock: + pipeline_status["history_messages"].append(no_file_msg) else: failed_deletions.append(doc_id) error_msg = f"Failed to delete document {i}/{total_docs}: {doc_id} - {result.message}" @@ -1395,7 +1444,7 @@ def create_document_routes( This operation is irreversible and will interact with the pipeline status. Args: - delete_request (DeleteDocRequest): The request containing the document IDs. + delete_request (DeleteDocRequest): The request containing the document IDs and delete_file options. background_tasks: FastAPI BackgroundTasks for async processing Returns: @@ -1433,7 +1482,13 @@ def create_document_routes( ) # Add deletion task to background tasks - background_tasks.add_task(background_delete_documents, rag, doc_ids) + background_tasks.add_task( + background_delete_documents, + rag, + doc_manager, + doc_ids, + delete_request.delete_file, + ) return DeleteDocByIdResponse( status="deletion_started", diff --git a/lightrag/base.py b/lightrag/base.py index add2318e..12d142c1 100644 --- a/lightrag/base.py +++ b/lightrag/base.py @@ -685,3 +685,4 @@ class DeletionResult: doc_id: str message: str status_code: int = 200 + file_path: str | None = None diff --git a/lightrag/lightrag.py b/lightrag/lightrag.py index 83ca2882..5e7d4153 100644 --- a/lightrag/lightrag.py +++ b/lightrag/lightrag.py @@ -1694,6 +1694,7 @@ class LightRAG: - `doc_id` (str): The ID of the document attempted to be deleted. - `message` (str): A summary of the operation's result. - `status_code` (int): HTTP status code (e.g., 200, 404, 500). + - `file_path` (str | None): The file path of the deleted document, if available. """ deletion_operations_started = False original_exception = None @@ -1961,11 +1962,15 @@ class LightRAG: logger.error(f"Failed to delete document and status: {e}") raise Exception(f"Failed to delete document and status: {e}") from e + # Get file path from document status for return value + file_path = doc_status_data.get("file_path") if doc_status_data else None + return DeletionResult( status="success", doc_id=doc_id, message=log_message, status_code=200, + file_path=file_path, ) except Exception as e: diff --git a/lightrag_webui/src/api/lightrag.ts b/lightrag_webui/src/api/lightrag.ts index 675ed4e4..8fa25d43 100644 --- a/lightrag_webui/src/api/lightrag.ts +++ b/lightrag_webui/src/api/lightrag.ts @@ -521,9 +521,9 @@ export const clearCache = async (modes?: string[]): Promise<{ return response.data } -export const deleteDocuments = async (docIds: string[]): Promise => { +export const deleteDocuments = async (docIds: string[], deleteFile: boolean = false): Promise => { const response = await axiosInstance.delete('/documents/delete_document', { - data: { doc_ids: docIds } + data: { doc_ids: docIds, delete_file: deleteFile } }) return response.data } diff --git a/lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx b/lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx index 7dbd4c40..acbf011e 100644 --- a/lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx +++ b/lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx @@ -43,6 +43,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo const { t } = useTranslation() const [open, setOpen] = useState(false) const [confirmText, setConfirmText] = useState('') + const [deleteFile, setDeleteFile] = useState(false) const [isDeleting, setIsDeleting] = useState(false) const isConfirmEnabled = confirmText.toLowerCase() === 'yes' && !isDeleting @@ -50,6 +51,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo useEffect(() => { if (!open) { setConfirmText('') + setDeleteFile(false) setIsDeleting(false) } }, [open]) @@ -65,7 +67,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo setIsDeleting(true) try { - const result = await deleteDocuments(selectedDocIds) + const result = await deleteDocuments(selectedDocIds, deleteFile) if (result.status === 'deletion_started') { toast.success(t('documentPanel.deleteDocuments.success', { count: selectedDocIds.length })) @@ -99,7 +101,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo } finally { setIsDeleting(false) } - }, [isConfirmEnabled, selectedDocIds, totalCompletedCount, setOpen, t, onDocumentsDeleted]) + }, [isConfirmEnabled, selectedDocIds, totalCompletedCount, deleteFile, setOpen, t, onDocumentsDeleted]) return ( @@ -146,6 +148,20 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo disabled={isDeleting} /> + +
+ setDeleteFile(e.target.checked)} + disabled={isDeleting} + className="h-4 w-4 text-red-600 focus:ring-red-500 border-gray-300 rounded" + /> + +
diff --git a/lightrag_webui/src/locales/ar.json b/lightrag_webui/src/locales/ar.json index 78ff985f..456fac32 100644 --- a/lightrag_webui/src/locales/ar.json +++ b/lightrag_webui/src/locales/ar.json @@ -66,7 +66,9 @@ "confirmPrompt": "اكتب 'yes' لتأكيد هذا الإجراء", "confirmPlaceholder": "اكتب yes للتأكيد", "confirmButton": "نعم", - "success": "تم حذف المستندات بنجاح", + "deleteFileOption": "حذف الملفات المرفوعة أيضًا", + "deleteFileTooltip": "حدد هذا الخيار لحذف الملفات المرفوعة المقابلة على الخادم أيضًا", + "success": "تم بدء تشغيل خط معالجة حذف المستندات بنجاح", "failed": "فشل حذف المستندات:\n{{message}}", "error": "فشل حذف المستندات:\n{{error}}", "busy": "خط المعالجة مشغول، يرجى المحاولة مرة أخرى لاحقًا", diff --git a/lightrag_webui/src/locales/en.json b/lightrag_webui/src/locales/en.json index ca63a6e8..ee64c1d8 100644 --- a/lightrag_webui/src/locales/en.json +++ b/lightrag_webui/src/locales/en.json @@ -66,7 +66,9 @@ "confirmPrompt": "Type 'yes' to confirm this action", "confirmPlaceholder": "Type yes to confirm", "confirmButton": "YES", - "success": "Documents deleted successfully", + "deleteFileOption": "Also delete uploaded files", + "deleteFileTooltip": "Check this option to also delete the corresponding uploaded files on the server", + "success": "Document deletion pipeline started successfully", "failed": "Delete Documents Failed:\n{{message}}", "error": "Delete Documents Failed:\n{{error}}", "busy": "Pipeline is busy, please try again later", diff --git a/lightrag_webui/src/locales/fr.json b/lightrag_webui/src/locales/fr.json index bbdf1940..6ca00f55 100644 --- a/lightrag_webui/src/locales/fr.json +++ b/lightrag_webui/src/locales/fr.json @@ -66,7 +66,9 @@ "confirmPrompt": "Tapez 'yes' pour confirmer cette action", "confirmPlaceholder": "Tapez yes pour confirmer", "confirmButton": "OUI", - "success": "Documents supprimés avec succès", + "deleteFileOption": "Supprimer également les fichiers téléchargés", + "deleteFileTooltip": "Cochez cette option pour supprimer également les fichiers téléchargés correspondants sur le serveur", + "success": "Pipeline de suppression de documents démarré avec succès", "failed": "Échec de la suppression des documents :\n{{message}}", "error": "Échec de la suppression des documents :\n{{error}}", "busy": "Le pipeline est occupé, veuillez réessayer plus tard", diff --git a/lightrag_webui/src/locales/zh.json b/lightrag_webui/src/locales/zh.json index 0e35d0d7..4ec466e2 100644 --- a/lightrag_webui/src/locales/zh.json +++ b/lightrag_webui/src/locales/zh.json @@ -66,7 +66,9 @@ "confirmPrompt": "请输入 yes 确认操作", "confirmPlaceholder": "输入 yes 确认", "confirmButton": "确定", - "success": "文档删除成功", + "deleteFileOption": "同时删除上传文件", + "deleteFileTooltip": "选中此选项将同时删除服务器上对应的上传文件", + "success": "文档删除流水线启动成功", "failed": "删除文档失败:\n{{message}}", "error": "删除文档失败:\n{{error}}", "busy": "流水线被占用,请稍后再试", diff --git a/lightrag_webui/src/locales/zh_TW.json b/lightrag_webui/src/locales/zh_TW.json index 4a1e0d79..18578e92 100644 --- a/lightrag_webui/src/locales/zh_TW.json +++ b/lightrag_webui/src/locales/zh_TW.json @@ -66,7 +66,9 @@ "confirmPrompt": "請輸入 yes 確認操作", "confirmPlaceholder": "輸入 yes 以確認", "confirmButton": "確定", - "success": "文件刪除成功", + "deleteFileOption": "同時刪除上傳檔案", + "deleteFileTooltip": "選取此選項將同時刪除伺服器上對應的上傳檔案", + "success": "文件刪除流水線啟動成功", "failed": "刪除文件失敗:\n{{message}}", "error": "刪除文件失敗:\n{{error}}", "busy": "pipeline 被佔用,請稍後再試",