Add LLM token
This commit is contained in:
@@ -526,10 +526,28 @@ class DocumentCommandService:
|
||||
logger.warning("临时文件清理失败: {}", temp_path)
|
||||
|
||||
def delete(self, doc_id: str) -> bool:
|
||||
"""Delete document record, binary file, and vector chunks."""
|
||||
"""Delete document record, binary file, and vector chunks.
|
||||
|
||||
Handles two cases:
|
||||
- Normal docs: have a metadata record in the document repository.
|
||||
- Milvus-only (synthetic) docs: visible in management-list because they
|
||||
have Milvus vectors but no JSON/PG metadata record. We still clean up
|
||||
the Milvus chunks so the document disappears from the list.
|
||||
"""
|
||||
document = self.document_repository.get(doc_id)
|
||||
if not document:
|
||||
# No metadata record — might be a Milvus-only synthetic document.
|
||||
# Attempt vector cleanup directly; treat as success if any chunks deleted.
|
||||
try:
|
||||
deleted_count = self.vector_index.delete_by_document(doc_id)
|
||||
if deleted_count > 0:
|
||||
logger.info("Deleted Milvus-only doc (no metadata record): doc_id={} chunks={}", doc_id, deleted_count)
|
||||
return True
|
||||
except Exception as exc:
|
||||
logger.warning("Milvus-only delete failed for doc_id={}: {}", doc_id, exc)
|
||||
return False
|
||||
|
||||
# Normal doc: clean up binary, vectors, artifacts, processing records, metadata.
|
||||
try:
|
||||
self.binary_store.delete(document.object_name)
|
||||
except Exception:
|
||||
@@ -627,13 +645,16 @@ class DocumentQueryService:
|
||||
result.append(doc)
|
||||
|
||||
# Surface Milvus-only docs that have no metadata record at all.
|
||||
# MinIO almost certainly has their binaries (they were uploaded), so
|
||||
# set object_name to the sentinel "{doc_id}/" so the route marks
|
||||
# has_file=True; the download endpoint will list MinIO to find the file.
|
||||
for doc_id, row in milvus_by_id.items():
|
||||
if doc_id not in meta_by_id:
|
||||
synthetic = Document(
|
||||
doc_id=doc_id,
|
||||
doc_name=row.get("doc_title", doc_id),
|
||||
file_name=row.get("doc_title", doc_id),
|
||||
object_name="",
|
||||
object_name=f"{doc_id}/", # sentinel: MinIO prefix exists
|
||||
content_type="",
|
||||
size_bytes=0,
|
||||
status=DocumentStatus.INDEXED,
|
||||
@@ -646,9 +667,63 @@ class DocumentQueryService:
|
||||
result.sort(key=lambda d: d.updated_at, reverse=True)
|
||||
return result[:limit] if limit is not None else result
|
||||
|
||||
def download(self, doc_id: str) -> tuple[Document, bytes]:
|
||||
"""Handle download for the Document Query Service instance."""
|
||||
def download(self, doc_id: str) -> tuple["Document", bytes]:
|
||||
"""Return the document record and its binary content from MinIO.
|
||||
|
||||
Fallback strategy for Milvus-only docs (no JSON/PG metadata record):
|
||||
1. Try metadata repository first (normal path).
|
||||
2. If metadata is missing, list MinIO objects with prefix ``{doc_id}/``
|
||||
and synthesise a minimal Document from the first object found.
|
||||
This handles documents whose metadata records were lost but whose
|
||||
binary files are still in object storage.
|
||||
3. If neither source has the file, raise FileNotFoundError.
|
||||
"""
|
||||
from app.domain.documents import Document, DocumentStatus
|
||||
|
||||
document = self.document_repository.get(doc_id)
|
||||
if not document:
|
||||
raise FileNotFoundError(f"文档不存在: {doc_id}")
|
||||
|
||||
if document and document.object_name and not document.object_name.endswith("/"):
|
||||
# Normal doc with a concrete object_name — read directly.
|
||||
return document, self.binary_store.read(document.object_name)
|
||||
|
||||
if document and not document.object_name:
|
||||
raise FileNotFoundError(f"该文档无原始文件(仅含索引数据,无法下载): {doc_id}")
|
||||
|
||||
if not document or document.object_name.endswith("/"):
|
||||
# Metadata missing — try to find the file in MinIO by doc_id prefix.
|
||||
try:
|
||||
objects = self.binary_store.list_objects(prefix=f"{doc_id}/")
|
||||
# Filter out artifact JSON files; prefer the source document.
|
||||
candidates = [o for o in objects if not o.endswith(".json")]
|
||||
if not candidates:
|
||||
candidates = objects # fall back to all objects if only JSON found
|
||||
if not candidates:
|
||||
raise FileNotFoundError(f"文档不存在(MinIO 和元数据均无记录): {doc_id}")
|
||||
object_name = candidates[0]
|
||||
file_name = object_name.split("/", 1)[-1] if "/" in object_name else object_name
|
||||
# Guess content type from extension.
|
||||
ext = file_name.rsplit(".", 1)[-1].lower() if "." in file_name else ""
|
||||
_ct_map = {
|
||||
"pdf": "application/pdf",
|
||||
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"doc": "application/msword",
|
||||
"txt": "text/plain",
|
||||
}
|
||||
content_type = _ct_map.get(ext, "application/octet-stream")
|
||||
# Synthesise a minimal Document so the route can build the response.
|
||||
document = Document(
|
||||
doc_id=doc_id,
|
||||
doc_name=file_name,
|
||||
file_name=file_name,
|
||||
object_name=object_name,
|
||||
content_type=content_type,
|
||||
size_bytes=0,
|
||||
status=DocumentStatus.INDEXED,
|
||||
)
|
||||
logger.info("MinIO fallback download: doc_id={} object={}", doc_id, object_name)
|
||||
except FileNotFoundError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise FileNotFoundError(f"文档不存在: {doc_id}") from exc
|
||||
|
||||
return document, self.binary_store.read(document.object_name)
|
||||
|
||||
Reference in New Issue
Block a user