Files
AIRegulation-DocAnalysis/backend/app/api/routes/documents.py

141 lines
4.8 KiB
Python

"""Define API routes for documents."""
from __future__ import annotations
from io import BytesIO
from urllib.parse import quote
from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from fastapi.responses import StreamingResponse
from loguru import logger
from app.api.models import DocumentUploadResponse
from app.application.documents import DocumentProcessResult
from app.shared.bootstrap import get_document_command_service, get_document_query_service
# Keep route handlers close to their transport-layer wiring for easier auditing.
router = APIRouter(prefix="/documents", tags=["documents"])
def _document_response(result: DocumentProcessResult) -> DocumentUploadResponse:
"""Handle document response for this module."""
return DocumentUploadResponse(
doc_id=result.doc_id,
doc_name=result.doc_name,
status=result.status,
message=result.message,
num_chunks=result.num_chunks,
summary=result.summary,
summary_latency_ms=result.summary_latency_ms,
)
@router.post("/upload", response_model=DocumentUploadResponse)
async def upload_document(
file: UploadFile = File(..., description="上传的文档文件"),
doc_name: str | None = Form(None, description="文档名称"),
regulation_type: str | None = Form(None, description="法规类型"),
version: str | None = Form(None, description="文档版本"),
generate_summary: bool = Form(False, description="是否生成摘要"),
):
"""Handle upload document."""
content = await file.read()
if not file.filename:
raise HTTPException(status_code=400, detail="文件名不能为空")
if not content:
raise HTTPException(status_code=400, detail="上传文件为空")
try:
result = get_document_command_service().upload_and_process(
file_name=file.filename,
content=content,
content_type=file.content_type or "application/octet-stream",
doc_name=doc_name,
regulation_type=regulation_type or "",
version=version or "",
generate_summary=generate_summary,
)
if result.status == "failed":
raise HTTPException(status_code=500, detail=result.message)
return _document_response(result)
except HTTPException:
raise
except Exception as exc:
logger.exception("文档上传失败")
raise HTTPException(status_code=500, detail=str(exc))
@router.get("/status/{doc_id}", response_model=DocumentUploadResponse)
async def get_document_status(doc_id: str):
"""Return document status."""
document = get_document_query_service().get(doc_id)
if not document:
raise HTTPException(status_code=404, detail="文档不存在")
return DocumentUploadResponse(
doc_id=document.doc_id,
doc_name=document.doc_name,
status=document.status.value,
message=document.error_message or "查询成功",
num_chunks=document.chunk_count,
summary=document.summary,
summary_latency_ms=document.summary_latency_ms,
)
@router.get("/download/{doc_id}")
async def download_document(doc_id: str):
"""Handle download document."""
try:
document, file_data = get_document_query_service().download(doc_id)
encoded_name = quote(document.file_name)
return StreamingResponse(
BytesIO(file_data),
media_type=document.content_type or "application/octet-stream",
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_name}"},
)
except FileNotFoundError as exc:
raise HTTPException(status_code=404, detail=str(exc))
except Exception as exc:
logger.exception("文档下载失败")
raise HTTPException(status_code=500, detail=str(exc))
@router.get("/list")
async def list_documents():
"""List documents."""
documents = get_document_query_service().list_documents()
return {
"documents": [
{
"doc_id": item.doc_id,
"doc_name": item.doc_name,
"status": item.status.value,
"chunk_count": item.chunk_count,
"updated_at": item.updated_at.isoformat(),
}
for item in documents
],
"total": len(documents),
}
@router.get("/management-list")
async def get_document_management_list():
"""Return document management list."""
documents = get_document_query_service().list_documents(limit=10)
return {
"documents": [
{
"doc_id": item.doc_id,
"doc_name": item.doc_name,
"status": item.status.value,
"chunk_count": item.chunk_count,
"updated_at": item.updated_at.isoformat(),
}
for item in documents
],
"total": len(documents),
"limit": 10,
}