2026-08-20 22:29:22 +02:00
|
|
|
"""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"])
|
2026-08-20 23:06:16 +02:00
|
|
|
question = body.get("question") or body.get("query", "")
|
2026-08-20 22:29:22 +02:00
|
|
|
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
|