"""Knowledge plugin routes — extraction, ask, review queue.""" from __future__ import annotations import uuid from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_db from app.deps import require_permission from app.plugins.builtins.knowledge.services import extract_knowledge, ask_knowledge, get_review_queue, review_extraction router = APIRouter(prefix="/api/v1/knowledge", tags=["knowledge"]) @router.post("/extract") async def extract( body: dict, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("wiki:read")), ): """Extract knowledge from a source (wiki article, dms file, mail, communication).""" tenant_id = uuid.UUID(current_user["tenant_id"]) source_type = body.get("source_type", "") source_id = body.get("source_id", "") source_title = body.get("source_title") source_text = body.get("source_text", "") if not source_type or not source_id or not source_text: raise HTTPException(400, detail={"detail": "source_type, source_id, source_text required", "code": "missing_fields"}) try: sid = uuid.UUID(source_id) except ValueError: raise HTTPException(400, detail={"detail": "Invalid source_id", "code": "invalid_id"}) from None result = await extract_knowledge( db=db, tenant_id=tenant_id, source_type=source_type, source_id=sid, source_title=source_title, source_text=source_text, user_id=uuid.UUID(current_user["user_id"]) if current_user.get("user_id") else None, ) return result @router.post("/ask") async def ask( body: dict, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("wiki:read")), ): """Ask a knowledge question — uses wiki + graph_rag as context.""" tenant_id = uuid.UUID(current_user["tenant_id"]) question = body.get("question") or body.get("query", "") if not question: raise HTTPException(400, detail={"detail": "question required", "code": "missing_question"}) result = await ask_knowledge(db=db, tenant_id=tenant_id, question=question) return result @router.get("/review") async def review_queue( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("wiki:read")), ): """Get pending knowledge extractions for review.""" tenant_id = uuid.UUID(current_user["tenant_id"]) return await get_review_queue(db=db, tenant_id=tenant_id, page=page, page_size=page_size) @router.post("/review/{extraction_id}") async def review( extraction_id: str, body: dict, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("wiki:write")), ): """Approve or reject a knowledge extraction.""" tenant_id = uuid.UUID(current_user["tenant_id"]) approved = body.get("approved", False) notes = body.get("notes") try: eid = uuid.UUID(extraction_id) except ValueError: raise HTTPException(400, detail={"detail": "Invalid extraction_id", "code": "invalid_id"}) from None result = await review_extraction( db=db, tenant_id=tenant_id, extraction_id=eid, approved=approved, user_id=uuid.UUID(current_user["user_id"]) if current_user.get("user_id") else None, notes=notes, ) if "error" in result: raise HTTPException(404, detail={"detail": result["error"], "code": "not_found"}) return result