""" 意图编制 API 路由 """ from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel from datetime import datetime from pathlib import Path from app.database import get_db from app.models.intent import Intent, AICheckRecord from planner.planning_agent.input_pipeline import parse_intent_file import tempfile router = APIRouter(prefix="/intent", tags=["意图编制"]) # ============ Pydantic Schemas ============ class IntentCreate(BaseModel): """创建意图请求""" title: str content: Optional[str] = None status: str = "draft" class IntentUpdate(BaseModel): """更新意图请求""" title: Optional[str] = None content: Optional[str] = None status: Optional[str] = None class IntentResponse(BaseModel): """意图响应""" id: int title: str content: Optional[str] status: str created_at: datetime updated_at: datetime class Config: from_attributes = True class IntentListResponse(BaseModel): """意图列表响应""" total: int items: List[IntentResponse] # ============ API Endpoints ============ @router.post("", response_model=IntentResponse, status_code=status.HTTP_201_CREATED) async def create_intent( intent_data: IntentCreate, db: AsyncSession = Depends(get_db) ): """创建新意图""" intent = Intent( title=intent_data.title, content=intent_data.content, status=intent_data.status ) db.add(intent) await db.commit() await db.refresh(intent) return intent @router.get("", response_model=IntentListResponse) async def list_intents( skip: int = 0, limit: int = 20, status_filter: Optional[str] = None, db: AsyncSession = Depends(get_db) ): """获取意图列表""" query = select(Intent) if status_filter: query = query.where(Intent.status == status_filter) query = query.order_by(Intent.updated_at.desc()) # 获取总数 count_query = select(Intent) if status_filter: count_query = count_query.where(Intent.status == status_filter) result = await db.execute(count_query) total = len(result.scalars().all()) # 获取分页数据 query = query.offset(skip).limit(limit) result = await db.execute(query) items = result.scalars().all() return IntentListResponse(total=total, items=items) @router.get("/{intent_id}", response_model=IntentResponse) async def get_intent( intent_id: int, db: AsyncSession = Depends(get_db) ): """获取单个意图""" result = await db.execute(select(Intent).where(Intent.id == intent_id)) intent = result.scalar_one_or_none() if not intent: raise HTTPException(status_code=404, detail="意图不存在") return intent @router.put("/{intent_id}", response_model=IntentResponse) async def update_intent( intent_id: int, intent_data: IntentUpdate, db: AsyncSession = Depends(get_db) ): """更新意图""" result = await db.execute(select(Intent).where(Intent.id == intent_id)) intent = result.scalar_one_or_none() if not intent: raise HTTPException(status_code=404, detail="意图不存在") if intent_data.title is not None: intent.title = intent_data.title if intent_data.content is not None: intent.content = intent_data.content if intent_data.status is not None: intent.status = intent_data.status intent.updated_at = datetime.utcnow() await db.commit() await db.refresh(intent) return intent @router.delete("/{intent_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_intent( intent_id: int, db: AsyncSession = Depends(get_db) ): """删除意图""" result = await db.execute(select(Intent).where(Intent.id == intent_id)) intent = result.scalar_one_or_none() if not intent: raise HTTPException(status_code=404, detail="意图不存在") await db.delete(intent) await db.commit() return None @router.post("/parse-file") async def parse_intent_file_endpoint(file: UploadFile = File(...)): """解析意图编制导入文件(pdf/图片走 MinerU,文本直接读取)""" if not file.filename: raise HTTPException(status_code=400, detail="未提供文件") suffix = Path(file.filename).suffix.lower() with tempfile.TemporaryDirectory() as tmp_dir: tmp_path = Path(tmp_dir) / file.filename content = await file.read() tmp_path.write_bytes(content) try: result = parse_intent_file(str(tmp_path)) except Exception as e: raise HTTPException(status_code=400, detail=str(e)) return { "filename": file.filename, "suffix": suffix, "title": result.get("title", ""), "content": result.get("content", ""), "raw_result": result.get("raw_result"), }