Feat: Add delete upload file option to document deletion
Browse files- lightrag/api/routers/document_routes.py +58 -3
- lightrag/base.py +1 -0
- lightrag/lightrag.py +5 -0
- lightrag_webui/src/api/lightrag.ts +2 -2
- lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx +18 -2
- lightrag_webui/src/locales/ar.json +3 -1
- lightrag_webui/src/locales/en.json +3 -1
- lightrag_webui/src/locales/fr.json +3 -1
- lightrag_webui/src/locales/zh.json +3 -1
- lightrag_webui/src/locales/zh_TW.json +3 -1
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(
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
"
|
|
|
|
|
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 |
-
"
|
|
|
|
|
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 |
-
"
|
|
|
|
|
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 |
-
"
|
|
|
|
|
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 |
-
"
|
|
|
|
|
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 被佔用,請稍後再試",
|