# PRD 上传与解析接口 import logging from concurrent.futures import ThreadPoolExecutor from fastapi import APIRouter, UploadFile, File, HTTPException from server.services.prd_manager.service import prd_service logger = logging.getLogger(__name__) router = APIRouter() ALLOWED_EXTENSIONS = {".md", ".txt", ".docx", ".pdf"} _parse_executor = ThreadPoolExecutor(max_workers=2) @router.post("/upload") async def upload_prd(file: UploadFile = File(...)): """上传 PRD 文件,立即返回 PRD ID,后台异步解析""" ext = "." + file.filename.rsplit(".", 1)[-1].lower() if "." in file.filename else "" if ext not in ALLOWED_EXTENSIONS: raise HTTPException(400, f"不支持的文件格式: {ext},支持: {', '.join(ALLOWED_EXTENSIONS)}") content = await file.read() if len(content) == 0: raise HTTPException(400, "文件为空") try: # Quick: save and return immediately prd_version = await prd_service.upload_quick(file.filename, content) # Background: parse async (image analysis may take 60s+) _parse_executor.submit(_parse_in_background, prd_version["prd_id"], file.filename, content) return { "prd_id": prd_version["prd_id"], "filename": prd_version["filename"], "status": "parsing", "uploaded_at": prd_version["uploaded_at"], } except Exception as e: logger.error("PRD upload failed: %s", e) raise HTTPException(500, f"上传失败: {e}") def _parse_in_background(prd_id: str, filename: str, content: bytes): """Run full parsing (text + images + vision analysis) in background thread.""" import asyncio try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(prd_service.parse_async(prd_id, filename, content)) except Exception as e: logger.error("Background parse failed for %s: %s", prd_id, e) prd_service.set_status(prd_id, "error", str(e)) @router.get("/{prd_id}/status") async def get_prd_status(prd_id: str): """获取 PRD 解析状态""" status = await prd_service.get_status(prd_id) if not status: raise HTTPException(404, "PRD not found") return status @router.get("/{prd_id}") async def get_prd(prd_id: str): """获取 PRD 详情与版本""" prd = await prd_service.get_prd(prd_id) if not prd: raise HTTPException(404, "PRD not found") return { "prd_id": prd["prd_id"], "filename": prd["filename"], "status": prd.get("status", "ready"), "full_text": prd.get("full_text", ""), "sections": prd.get("sections", []), "images": prd.get("images", []), "uploaded_at": prd["uploaded_at"], }