gzdaniel commited on
Commit
55629a6
·
1 Parent(s): c04a4dc

Feat: Add delete upload file option to document deletion

Browse files
lightrag/api/routers/document_routes.py CHANGED
@@ -261,6 +261,10 @@ Attributes:
261
 
262
  class DeleteDocRequest(BaseModel):
263
  doc_ids: List[str] = Field(..., description="The IDs of the documents to delete.")
 
 
 
 
264
 
265
  @field_validator("doc_ids", mode="after")
266
  @classmethod
@@ -793,7 +797,12 @@ async def run_scanning_process(rag: LightRAG, doc_manager: DocumentManager):
793
  logger.error(traceback.format_exc())
794
 
795
 
796
- async def background_delete_documents(rag: LightRAG, doc_ids: List[str]):
 
 
 
 
 
797
  """Background task to delete multiple documents"""
798
  from lightrag.kg.shared_storage import (
799
  get_namespace_data,
@@ -847,6 +856,46 @@ async def background_delete_documents(rag: LightRAG, doc_ids: List[str]):
847
 
848
  async with pipeline_status_lock:
849
  pipeline_status["history_messages"].append(success_msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
850
  else:
851
  failed_deletions.append(doc_id)
852
  error_msg = f"Failed to delete document {i}/{total_docs}: {doc_id} - {result.message}"
@@ -1395,7 +1444,7 @@ def create_document_routes(
1395
  This operation is irreversible and will interact with the pipeline status.
1396
 
1397
  Args:
1398
- delete_request (DeleteDocRequest): The request containing the document IDs.
1399
  background_tasks: FastAPI BackgroundTasks for async processing
1400
 
1401
  Returns:
@@ -1433,7 +1482,13 @@ def create_document_routes(
1433
  )
1434
 
1435
  # Add deletion task to background tasks
1436
- background_tasks.add_task(background_delete_documents, rag, doc_ids)
 
 
 
 
 
 
1437
 
1438
  return DeleteDocByIdResponse(
1439
  status="deletion_started",
 
261
 
262
  class DeleteDocRequest(BaseModel):
263
  doc_ids: List[str] = Field(..., description="The IDs of the documents to delete.")
264
+ delete_file: bool = Field(
265
+ default=False,
266
+ description="Whether to delete the corresponding file in the upload directory.",
267
+ )
268
 
269
  @field_validator("doc_ids", mode="after")
270
  @classmethod
 
797
  logger.error(traceback.format_exc())
798
 
799
 
800
+ async def background_delete_documents(
801
+ rag: LightRAG,
802
+ doc_manager: DocumentManager,
803
+ doc_ids: List[str],
804
+ delete_file: bool = False,
805
+ ):
806
  """Background task to delete multiple documents"""
807
  from lightrag.kg.shared_storage import (
808
  get_namespace_data,
 
856
 
857
  async with pipeline_status_lock:
858
  pipeline_status["history_messages"].append(success_msg)
859
+
860
+ # Handle file deletion if requested and file_path is available
861
+ if (
862
+ delete_file
863
+ and result.file_path
864
+ and result.file_path != "unknown_source"
865
+ ):
866
+ try:
867
+ file_path = doc_manager.input_dir / result.file_path
868
+ if file_path.exists():
869
+ file_path.unlink()
870
+ file_delete_msg = (
871
+ f"Successfully deleted file: {result.file_path}"
872
+ )
873
+ logger.info(file_delete_msg)
874
+ async with pipeline_status_lock:
875
+ pipeline_status["history_messages"].append(
876
+ file_delete_msg
877
+ )
878
+ else:
879
+ file_not_found_msg = (
880
+ f"File not found for deletion: {result.file_path}"
881
+ )
882
+ logger.warning(file_not_found_msg)
883
+ async with pipeline_status_lock:
884
+ pipeline_status["history_messages"].append(
885
+ file_not_found_msg
886
+ )
887
+ except Exception as file_error:
888
+ file_error_msg = f"Failed to delete file {result.file_path}: {str(file_error)}"
889
+ logger.error(file_error_msg)
890
+ async with pipeline_status_lock:
891
+ pipeline_status["history_messages"].append(
892
+ file_error_msg
893
+ )
894
+ elif delete_file:
895
+ no_file_msg = f"No valid file path found for document {doc_id}"
896
+ logger.warning(no_file_msg)
897
+ async with pipeline_status_lock:
898
+ pipeline_status["history_messages"].append(no_file_msg)
899
  else:
900
  failed_deletions.append(doc_id)
901
  error_msg = f"Failed to delete document {i}/{total_docs}: {doc_id} - {result.message}"
 
1444
  This operation is irreversible and will interact with the pipeline status.
1445
 
1446
  Args:
1447
+ delete_request (DeleteDocRequest): The request containing the document IDs and delete_file options.
1448
  background_tasks: FastAPI BackgroundTasks for async processing
1449
 
1450
  Returns:
 
1482
  )
1483
 
1484
  # Add deletion task to background tasks
1485
+ background_tasks.add_task(
1486
+ background_delete_documents,
1487
+ rag,
1488
+ doc_manager,
1489
+ doc_ids,
1490
+ delete_request.delete_file,
1491
+ )
1492
 
1493
  return DeleteDocByIdResponse(
1494
  status="deletion_started",
lightrag/base.py CHANGED
@@ -685,3 +685,4 @@ class DeletionResult:
685
  doc_id: str
686
  message: str
687
  status_code: int = 200
 
 
685
  doc_id: str
686
  message: str
687
  status_code: int = 200
688
+ file_path: str | None = None
lightrag/lightrag.py CHANGED
@@ -1694,6 +1694,7 @@ class LightRAG:
1694
  - `doc_id` (str): The ID of the document attempted to be deleted.
1695
  - `message` (str): A summary of the operation's result.
1696
  - `status_code` (int): HTTP status code (e.g., 200, 404, 500).
 
1697
  """
1698
  deletion_operations_started = False
1699
  original_exception = None
@@ -1961,11 +1962,15 @@ class LightRAG:
1961
  logger.error(f"Failed to delete document and status: {e}")
1962
  raise Exception(f"Failed to delete document and status: {e}") from e
1963
 
 
 
 
1964
  return DeletionResult(
1965
  status="success",
1966
  doc_id=doc_id,
1967
  message=log_message,
1968
  status_code=200,
 
1969
  )
1970
 
1971
  except Exception as e:
 
1694
  - `doc_id` (str): The ID of the document attempted to be deleted.
1695
  - `message` (str): A summary of the operation's result.
1696
  - `status_code` (int): HTTP status code (e.g., 200, 404, 500).
1697
+ - `file_path` (str | None): The file path of the deleted document, if available.
1698
  """
1699
  deletion_operations_started = False
1700
  original_exception = None
 
1962
  logger.error(f"Failed to delete document and status: {e}")
1963
  raise Exception(f"Failed to delete document and status: {e}") from e
1964
 
1965
+ # Get file path from document status for return value
1966
+ file_path = doc_status_data.get("file_path") if doc_status_data else None
1967
+
1968
  return DeletionResult(
1969
  status="success",
1970
  doc_id=doc_id,
1971
  message=log_message,
1972
  status_code=200,
1973
+ file_path=file_path,
1974
  )
1975
 
1976
  except Exception as e:
lightrag_webui/src/api/lightrag.ts CHANGED
@@ -521,9 +521,9 @@ export const clearCache = async (modes?: string[]): Promise<{
521
  return response.data
522
  }
523
 
524
- export const deleteDocuments = async (docIds: string[]): Promise<DeleteDocResponse> => {
525
  const response = await axiosInstance.delete('/documents/delete_document', {
526
- data: { doc_ids: docIds }
527
  })
528
  return response.data
529
  }
 
521
  return response.data
522
  }
523
 
524
+ export const deleteDocuments = async (docIds: string[], deleteFile: boolean = false): Promise<DeleteDocResponse> => {
525
  const response = await axiosInstance.delete('/documents/delete_document', {
526
+ data: { doc_ids: docIds, delete_file: deleteFile }
527
  })
528
  return response.data
529
  }
lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx CHANGED
@@ -43,6 +43,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo
43
  const { t } = useTranslation()
44
  const [open, setOpen] = useState(false)
45
  const [confirmText, setConfirmText] = useState('')
 
46
  const [isDeleting, setIsDeleting] = useState(false)
47
  const isConfirmEnabled = confirmText.toLowerCase() === 'yes' && !isDeleting
48
 
@@ -50,6 +51,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo
50
  useEffect(() => {
51
  if (!open) {
52
  setConfirmText('')
 
53
  setIsDeleting(false)
54
  }
55
  }, [open])
@@ -65,7 +67,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo
65
 
66
  setIsDeleting(true)
67
  try {
68
- const result = await deleteDocuments(selectedDocIds)
69
 
70
  if (result.status === 'deletion_started') {
71
  toast.success(t('documentPanel.deleteDocuments.success', { count: selectedDocIds.length }))
@@ -99,7 +101,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo
99
  } finally {
100
  setIsDeleting(false)
101
  }
102
- }, [isConfirmEnabled, selectedDocIds, totalCompletedCount, setOpen, t, onDocumentsDeleted])
103
 
104
  return (
105
  <Dialog open={open} onOpenChange={setOpen}>
@@ -146,6 +148,20 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo
146
  disabled={isDeleting}
147
  />
148
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  </div>
150
 
151
  <DialogFooter>
 
43
  const { t } = useTranslation()
44
  const [open, setOpen] = useState(false)
45
  const [confirmText, setConfirmText] = useState('')
46
+ const [deleteFile, setDeleteFile] = useState(false)
47
  const [isDeleting, setIsDeleting] = useState(false)
48
  const isConfirmEnabled = confirmText.toLowerCase() === 'yes' && !isDeleting
49
 
 
51
  useEffect(() => {
52
  if (!open) {
53
  setConfirmText('')
54
+ setDeleteFile(false)
55
  setIsDeleting(false)
56
  }
57
  }, [open])
 
67
 
68
  setIsDeleting(true)
69
  try {
70
+ const result = await deleteDocuments(selectedDocIds, deleteFile)
71
 
72
  if (result.status === 'deletion_started') {
73
  toast.success(t('documentPanel.deleteDocuments.success', { count: selectedDocIds.length }))
 
101
  } finally {
102
  setIsDeleting(false)
103
  }
104
+ }, [isConfirmEnabled, selectedDocIds, totalCompletedCount, deleteFile, setOpen, t, onDocumentsDeleted])
105
 
106
  return (
107
  <Dialog open={open} onOpenChange={setOpen}>
 
148
  disabled={isDeleting}
149
  />
150
  </div>
151
+
152
+ <div className="flex items-center space-x-2">
153
+ <input
154
+ type="checkbox"
155
+ id="delete-file"
156
+ checked={deleteFile}
157
+ onChange={(e) => setDeleteFile(e.target.checked)}
158
+ disabled={isDeleting}
159
+ className="h-4 w-4 text-red-600 focus:ring-red-500 border-gray-300 rounded"
160
+ />
161
+ <Label htmlFor="delete-file" className="text-sm font-medium cursor-pointer">
162
+ {t('documentPanel.deleteDocuments.deleteFileOption')}
163
+ </Label>
164
+ </div>
165
  </div>
166
 
167
  <DialogFooter>
lightrag_webui/src/locales/ar.json CHANGED
@@ -66,7 +66,9 @@
66
  "confirmPrompt": "اكتب 'yes' لتأكيد هذا الإجراء",
67
  "confirmPlaceholder": "اكتب yes للتأكيد",
68
  "confirmButton": "نعم",
69
- "success": "تم حذف المستندات بنجاح",
 
 
70
  "failed": "فشل حذف المستندات:\n{{message}}",
71
  "error": "فشل حذف المستندات:\n{{error}}",
72
  "busy": "خط المعالجة مشغول، يرجى المحاولة مرة أخرى لاحقًا",
 
66
  "confirmPrompt": "اكتب 'yes' لتأكيد هذا الإجراء",
67
  "confirmPlaceholder": "اكتب yes للتأكيد",
68
  "confirmButton": "نعم",
69
+ "deleteFileOption": "حذف الملفات المرفوعة أيضًا",
70
+ "deleteFileTooltip": "حدد هذا الخيار لحذف الملفات المرفوعة المقابلة على الخادم أيضًا",
71
+ "success": "تم بدء تشغيل خط معالجة حذف المستندات بنجاح",
72
  "failed": "فشل حذف المستندات:\n{{message}}",
73
  "error": "فشل حذف المستندات:\n{{error}}",
74
  "busy": "خط المعالجة مشغول، يرجى المحاولة مرة أخرى لاحقًا",
lightrag_webui/src/locales/en.json CHANGED
@@ -66,7 +66,9 @@
66
  "confirmPrompt": "Type 'yes' to confirm this action",
67
  "confirmPlaceholder": "Type yes to confirm",
68
  "confirmButton": "YES",
69
- "success": "Documents deleted successfully",
 
 
70
  "failed": "Delete Documents Failed:\n{{message}}",
71
  "error": "Delete Documents Failed:\n{{error}}",
72
  "busy": "Pipeline is busy, please try again later",
 
66
  "confirmPrompt": "Type 'yes' to confirm this action",
67
  "confirmPlaceholder": "Type yes to confirm",
68
  "confirmButton": "YES",
69
+ "deleteFileOption": "Also delete uploaded files",
70
+ "deleteFileTooltip": "Check this option to also delete the corresponding uploaded files on the server",
71
+ "success": "Document deletion pipeline started successfully",
72
  "failed": "Delete Documents Failed:\n{{message}}",
73
  "error": "Delete Documents Failed:\n{{error}}",
74
  "busy": "Pipeline is busy, please try again later",
lightrag_webui/src/locales/fr.json CHANGED
@@ -66,7 +66,9 @@
66
  "confirmPrompt": "Tapez 'yes' pour confirmer cette action",
67
  "confirmPlaceholder": "Tapez yes pour confirmer",
68
  "confirmButton": "OUI",
69
- "success": "Documents supprimés avec succès",
 
 
70
  "failed": "Échec de la suppression des documents :\n{{message}}",
71
  "error": "Échec de la suppression des documents :\n{{error}}",
72
  "busy": "Le pipeline est occupé, veuillez réessayer plus tard",
 
66
  "confirmPrompt": "Tapez 'yes' pour confirmer cette action",
67
  "confirmPlaceholder": "Tapez yes pour confirmer",
68
  "confirmButton": "OUI",
69
+ "deleteFileOption": "Supprimer également les fichiers téléchargés",
70
+ "deleteFileTooltip": "Cochez cette option pour supprimer également les fichiers téléchargés correspondants sur le serveur",
71
+ "success": "Pipeline de suppression de documents démarré avec succès",
72
  "failed": "Échec de la suppression des documents :\n{{message}}",
73
  "error": "Échec de la suppression des documents :\n{{error}}",
74
  "busy": "Le pipeline est occupé, veuillez réessayer plus tard",
lightrag_webui/src/locales/zh.json CHANGED
@@ -66,7 +66,9 @@
66
  "confirmPrompt": "请输入 yes 确认操作",
67
  "confirmPlaceholder": "输入 yes 确认",
68
  "confirmButton": "确定",
69
- "success": "文档删除成功",
 
 
70
  "failed": "删除文档失败:\n{{message}}",
71
  "error": "删除文档失败:\n{{error}}",
72
  "busy": "流水线被占用,请稍后再试",
 
66
  "confirmPrompt": "请输入 yes 确认操作",
67
  "confirmPlaceholder": "输入 yes 确认",
68
  "confirmButton": "确定",
69
+ "deleteFileOption": "同时删除上传文件",
70
+ "deleteFileTooltip": "选中此选项将同时删除服务器上对应的上传文件",
71
+ "success": "文档删除流水线启动成功",
72
  "failed": "删除文档失败:\n{{message}}",
73
  "error": "删除文档失败:\n{{error}}",
74
  "busy": "流水线被占用,请稍后再试",
lightrag_webui/src/locales/zh_TW.json CHANGED
@@ -66,7 +66,9 @@
66
  "confirmPrompt": "請輸入 yes 確認操作",
67
  "confirmPlaceholder": "輸入 yes 以確認",
68
  "confirmButton": "確定",
69
- "success": "文件刪除成功",
 
 
70
  "failed": "刪除文件失敗:\n{{message}}",
71
  "error": "刪除文件失敗:\n{{error}}",
72
  "busy": "pipeline 被佔用,請稍後再試",
 
66
  "confirmPrompt": "請輸入 yes 確認操作",
67
  "confirmPlaceholder": "輸入 yes 以確認",
68
  "confirmButton": "確定",
69
+ "deleteFileOption": "同時刪除上傳檔案",
70
+ "deleteFileTooltip": "選取此選項將同時刪除伺服器上對應的上傳檔案",
71
+ "success": "文件刪除流水線啟動成功",
72
  "failed": "刪除文件失敗:\n{{message}}",
73
  "error": "刪除文件失敗:\n{{error}}",
74
  "busy": "pipeline 被佔用,請稍後再試",