Feat: Add delete upload file option to document deletion

This commit is contained in:
yangdx 2025-06-25 19:02:46 +08:00
parent 6a1737784d
commit bdcd55a871
10 changed files with 99 additions and 12 deletions

View File

@ -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",

View File

@ -685,3 +685,4 @@ class DeletionResult:
doc_id: str
message: str
status_code: int = 200
file_path: str | None = None

View File

@ -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:

View File

@ -521,9 +521,9 @@ export const clearCache = async (modes?: string[]): Promise<{
return response.data
}
export const deleteDocuments = async (docIds: string[]): Promise<DeleteDocResponse> => {
export const deleteDocuments = async (docIds: string[], deleteFile: boolean = false): Promise<DeleteDocResponse> => {
const response = await axiosInstance.delete('/documents/delete_document', {
data: { doc_ids: docIds }
data: { doc_ids: docIds, delete_file: deleteFile }
})
return response.data
}

View File

@ -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 (
<Dialog open={open} onOpenChange={setOpen}>
@ -146,6 +148,20 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo
disabled={isDeleting}
/>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="delete-file"
checked={deleteFile}
onChange={(e) => setDeleteFile(e.target.checked)}
disabled={isDeleting}
className="h-4 w-4 text-red-600 focus:ring-red-500 border-gray-300 rounded"
/>
<Label htmlFor="delete-file" className="text-sm font-medium cursor-pointer">
{t('documentPanel.deleteDocuments.deleteFileOption')}
</Label>
</div>
</div>
<DialogFooter>

View File

@ -66,7 +66,9 @@
"confirmPrompt": "اكتب 'yes' لتأكيد هذا الإجراء",
"confirmPlaceholder": "اكتب yes للتأكيد",
"confirmButton": "نعم",
"success": "تم حذف المستندات بنجاح",
"deleteFileOption": "حذف الملفات المرفوعة أيضًا",
"deleteFileTooltip": "حدد هذا الخيار لحذف الملفات المرفوعة المقابلة على الخادم أيضًا",
"success": "تم بدء تشغيل خط معالجة حذف المستندات بنجاح",
"failed": "فشل حذف المستندات:\n{{message}}",
"error": "فشل حذف المستندات:\n{{error}}",
"busy": "خط المعالجة مشغول، يرجى المحاولة مرة أخرى لاحقًا",

View File

@ -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",

View File

@ -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",

View File

@ -66,7 +66,9 @@
"confirmPrompt": "请输入 yes 确认操作",
"confirmPlaceholder": "输入 yes 确认",
"confirmButton": "确定",
"success": "文档删除成功",
"deleteFileOption": "同时删除上传文件",
"deleteFileTooltip": "选中此选项将同时删除服务器上对应的上传文件",
"success": "文档删除流水线启动成功",
"failed": "删除文档失败:\n{{message}}",
"error": "删除文档失败:\n{{error}}",
"busy": "流水线被占用,请稍后再试",

View File

@ -66,7 +66,9 @@
"confirmPrompt": "請輸入 yes 確認操作",
"confirmPlaceholder": "輸入 yes 以確認",
"confirmButton": "確定",
"success": "文件刪除成功",
"deleteFileOption": "同時刪除上傳檔案",
"deleteFileTooltip": "選取此選項將同時刪除伺服器上對應的上傳檔案",
"success": "文件刪除流水線啟動成功",
"failed": "刪除文件失敗:\n{{message}}",
"error": "刪除文件失敗:\n{{error}}",
"busy": "pipeline 被佔用,請稍後再試",