184 lines
5.2 KiB
Python
184 lines
5.2 KiB
Python
"""
|
|
Knowledge Base management endpoints.
|
|
[AC-ASA-01, AC-ASA-02, AC-ASA-08] Document upload, list, and index job status.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Annotated, Any, Optional
|
|
|
|
from fastapi import APIRouter, Depends, Header, Query, UploadFile, File, Form
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.core.tenant import get_tenant_id
|
|
from app.models import ErrorResponse
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/admin/kb", tags=["KB Management"])
|
|
|
|
|
|
@router.get(
|
|
"/documents",
|
|
operation_id="listDocuments",
|
|
summary="Query document list",
|
|
description="[AC-ASA-08] Get list of documents with pagination and filtering.",
|
|
responses={
|
|
200: {"description": "Document list with pagination"},
|
|
401: {"description": "Unauthorized", "model": ErrorResponse},
|
|
403: {"description": "Forbidden", "model": ErrorResponse},
|
|
},
|
|
)
|
|
async def list_documents(
|
|
tenant_id: Annotated[str, Depends(get_tenant_id)],
|
|
kb_id: Annotated[Optional[str], Query()] = None,
|
|
status: Annotated[Optional[str], Query()] = None,
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
) -> JSONResponse:
|
|
"""
|
|
[AC-ASA-08] List documents with filtering and pagination.
|
|
"""
|
|
logger.info(
|
|
f"[AC-ASA-08] Listing documents: tenant={tenant_id}, kb_id={kb_id}, "
|
|
f"status={status}, page={page}, page_size={page_size}"
|
|
)
|
|
|
|
mock_documents = [
|
|
{
|
|
"docId": "doc_001",
|
|
"kbId": kb_id or "kb_default",
|
|
"fileName": "product_manual.pdf",
|
|
"status": "completed",
|
|
"createdAt": "2026-02-20T10:00:00Z",
|
|
"updatedAt": "2026-02-20T10:30:00Z",
|
|
},
|
|
{
|
|
"docId": "doc_002",
|
|
"kbId": kb_id or "kb_default",
|
|
"fileName": "faq.docx",
|
|
"status": "processing",
|
|
"createdAt": "2026-02-21T14:00:00Z",
|
|
"updatedAt": "2026-02-21T14:15:00Z",
|
|
},
|
|
{
|
|
"docId": "doc_003",
|
|
"kbId": kb_id or "kb_default",
|
|
"fileName": "invalid_file.txt",
|
|
"status": "failed",
|
|
"createdAt": "2026-02-22T09:00:00Z",
|
|
"updatedAt": "2026-02-22T09:05:00Z",
|
|
},
|
|
]
|
|
|
|
filtered = mock_documents
|
|
if kb_id:
|
|
filtered = [d for d in filtered if d["kbId"] == kb_id]
|
|
if status:
|
|
filtered = [d for d in filtered if d["status"] == status]
|
|
|
|
total = len(filtered)
|
|
total_pages = (total + page_size - 1) // page_size
|
|
|
|
return JSONResponse(
|
|
content={
|
|
"data": filtered,
|
|
"pagination": {
|
|
"page": page,
|
|
"pageSize": page_size,
|
|
"total": total,
|
|
"totalPages": total_pages,
|
|
},
|
|
}
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/documents",
|
|
operation_id="uploadDocument",
|
|
summary="Upload/import document",
|
|
description="[AC-ASA-01] Upload document and trigger indexing job.",
|
|
responses={
|
|
202: {"description": "Accepted - async indexing job started"},
|
|
401: {"description": "Unauthorized", "model": ErrorResponse},
|
|
403: {"description": "Forbidden", "model": ErrorResponse},
|
|
},
|
|
)
|
|
async def upload_document(
|
|
tenant_id: Annotated[str, Depends(get_tenant_id)],
|
|
file: UploadFile = File(...),
|
|
kb_id: str = Form(...),
|
|
) -> JSONResponse:
|
|
"""
|
|
[AC-ASA-01] Upload document and create indexing job.
|
|
"""
|
|
logger.info(
|
|
f"[AC-ASA-01] Uploading document: tenant={tenant_id}, "
|
|
f"kb_id={kb_id}, filename={file.filename}"
|
|
)
|
|
|
|
import uuid
|
|
|
|
job_id = f"job_{uuid.uuid4().hex[:8]}"
|
|
|
|
return JSONResponse(
|
|
status_code=202,
|
|
content={
|
|
"jobId": job_id,
|
|
"status": "pending",
|
|
},
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/index/jobs/{job_id}",
|
|
operation_id="getIndexJob",
|
|
summary="Query index job status",
|
|
description="[AC-ASA-02] Get indexing job status and progress.",
|
|
responses={
|
|
200: {"description": "Job status details"},
|
|
401: {"description": "Unauthorized", "model": ErrorResponse},
|
|
403: {"description": "Forbidden", "model": ErrorResponse},
|
|
},
|
|
)
|
|
async def get_index_job(
|
|
tenant_id: Annotated[str, Depends(get_tenant_id)],
|
|
job_id: str,
|
|
) -> JSONResponse:
|
|
"""
|
|
[AC-ASA-02] Get indexing job status with progress.
|
|
"""
|
|
logger.info(
|
|
f"[AC-ASA-02] Getting job status: tenant={tenant_id}, job_id={job_id}"
|
|
)
|
|
|
|
mock_job_statuses = {
|
|
"job_pending": {
|
|
"jobId": job_id,
|
|
"status": "pending",
|
|
"progress": 0,
|
|
"errorMsg": None,
|
|
},
|
|
"job_processing": {
|
|
"jobId": job_id,
|
|
"status": "processing",
|
|
"progress": 45,
|
|
"errorMsg": None,
|
|
},
|
|
"job_completed": {
|
|
"jobId": job_id,
|
|
"status": "completed",
|
|
"progress": 100,
|
|
"errorMsg": None,
|
|
},
|
|
"job_failed": {
|
|
"jobId": job_id,
|
|
"status": "failed",
|
|
"progress": 30,
|
|
"errorMsg": "Failed to parse PDF: Invalid format",
|
|
},
|
|
}
|
|
|
|
job_status = mock_job_statuses.get(job_id, mock_job_statuses["job_processing"])
|
|
|
|
return JSONResponse(content=job_status)
|