wx-agent/wiki_engine/routes.py

231 lines
6.8 KiB
Python

from datetime import datetime
from typing import Optional
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from wiki_engine import db
from wiki_engine import service
router = APIRouter(prefix="/api/wiki", tags=["wiki"])
class WikiSyncFileRequest(BaseModel):
kb_id: Optional[str] = None
kb_name: Optional[str] = None
use_llm: bool = False
background: bool = True
class WikiSyncKbRequest(BaseModel):
kb_id: Optional[str] = None
kb_name: Optional[str] = None
limit: Optional[int] = None
use_llm: bool = False
background: bool = True
class WikiSearchRequest(BaseModel):
query: str
kb_id: Optional[str] = None
kb_name: Optional[str] = None
limit: int = 8
include_slices: bool = True
class WikiDeleteKbRequest(BaseModel):
kb_id: Optional[str] = None
kb_name: Optional[str] = None
class WikiDeleteFileRequest(BaseModel):
file_id: str
class WikiDeleteTimeRequest(BaseModel):
before: Optional[datetime] = None
after: Optional[datetime] = None
kb_id: Optional[str] = None
kb_name: Optional[str] = None
class WikiDiffKbRequest(BaseModel):
kb_id: Optional[str] = None
kb_name: Optional[str] = None
include_children: bool = False
limit: int = 200
parsed_only: bool = True
class WikiReconcileKbRequest(BaseModel):
kb_id: Optional[str] = None
kb_name: Optional[str] = None
include_children: bool = False
limit: int = 200
sync_missing: bool = True
rebuild_stale: bool = True
delete_orphan: bool = False
use_llm: bool = False
dry_run: bool = True
@router.post("/init")
async def init_wiki():
await db.init_wiki_tables()
return {"success": True}
@router.post("/sync/file/{file_id}")
async def sync_file(file_id: str, request: WikiSyncFileRequest):
kb_id = await _resolve_optional_kb_id(request.kb_id, request.kb_name)
if request.background:
job_id = await service.start_sync_job(
target_type="file",
target_id=file_id,
kb_id=kb_id,
use_llm=request.use_llm,
)
return {"success": True, "job_id": job_id, "status": "running", "job": await db.get_job(job_id)}
return await service.sync_file(file_id=file_id, kb_id=kb_id, use_llm=request.use_llm)
@router.post("/sync/kb")
async def sync_kb(request: WikiSyncKbRequest):
kb_id = await _resolve_required_kb_id(request.kb_id, request.kb_name)
if request.background:
job_id = await service.start_sync_job(
target_type="kb",
target_id=kb_id,
kb_id=kb_id,
limit=request.limit,
use_llm=request.use_llm,
)
return {"success": True, "job_id": job_id, "status": "running", "job": await db.get_job(job_id)}
return await service.sync_kb(kb_id=kb_id, limit=request.limit, use_llm=request.use_llm)
@router.get("/sync/jobs/{job_id}")
async def get_sync_job(job_id: str):
job = await db.get_job(job_id)
if not job:
raise HTTPException(status_code=404, detail="sync job not found")
return {"success": True, "job": job}
@router.post("/search")
async def search_wiki(request: WikiSearchRequest):
if not request.query.strip():
raise HTTPException(status_code=400, detail="query is empty")
kb_id = await _resolve_optional_kb_id(request.kb_id, request.kb_name)
return await service.search_wiki(
query=request.query,
kb_id=kb_id,
limit=request.limit,
include_slices=request.include_slices,
)
@router.post("/evidence/search")
async def search_evidence(request: WikiSearchRequest):
if not request.query.strip():
raise HTTPException(status_code=400, detail="query is empty")
kb_id = await _resolve_optional_kb_id(request.kb_id, request.kb_name)
return await service.evidence_search(
query=request.query,
kb_id=kb_id,
limit=request.limit,
)
@router.get("/pages/{page_id}")
async def get_page(page_id: int):
page = await service.get_page(page_id)
if not page:
raise HTTPException(status_code=404, detail="wiki page not found")
return {"success": True, "page": page}
@router.get("/stats")
async def get_stats():
return {"success": True, "stats": await service.get_stats()}
@router.get("/queue/stats")
async def get_queue_stats():
return {"success": True, "stats": await service.get_queue_stats()}
@router.get("/tree")
async def get_kb_tree():
return await service.get_kb_tree()
@router.post("/diff/kb")
async def diff_kb(request: WikiDiffKbRequest):
kb_id = await _resolve_required_kb_id(request.kb_id, request.kb_name)
return await service.diff_kb(
kb_id=kb_id,
include_children=request.include_children,
limit=request.limit,
parsed_only=request.parsed_only,
)
@router.post("/reconcile/kb")
async def reconcile_kb(request: WikiReconcileKbRequest):
kb_id = await _resolve_required_kb_id(request.kb_id, request.kb_name)
return await service.reconcile_kb(
kb_id=kb_id,
include_children=request.include_children,
limit=request.limit,
sync_missing=request.sync_missing,
rebuild_stale=request.rebuild_stale,
delete_orphan=request.delete_orphan,
use_llm=request.use_llm,
dry_run=request.dry_run,
)
@router.post("/delete/by-kb")
async def delete_by_kb(request: WikiDeleteKbRequest):
kb_id = await _resolve_required_kb_id(request.kb_id, request.kb_name)
return await service.delete_by_kb(kb_id)
@router.post("/delete/by-file")
async def delete_by_file(request: WikiDeleteFileRequest):
if not request.file_id.strip():
raise HTTPException(status_code=400, detail="file_id is empty")
return await service.delete_by_file(request.file_id)
@router.post("/delete/by-time")
async def delete_by_time(request: WikiDeleteTimeRequest):
if request.before is None and request.after is None:
raise HTTPException(status_code=400, detail="before or after is required")
kb_id = await _resolve_optional_kb_id(request.kb_id, request.kb_name)
return await service.delete_by_time(
before=request.before,
after=request.after,
kb_id=kb_id,
)
async def _resolve_required_kb_id(kb_id: Optional[str], kb_name: Optional[str]) -> str:
resolved = await _resolve_optional_kb_id(kb_id, kb_name)
if not resolved:
raise HTTPException(status_code=400, detail="kb_name or kb_id is required")
return resolved
async def _resolve_optional_kb_id(kb_id: Optional[str], kb_name: Optional[str]) -> Optional[str]:
if kb_id and kb_id.strip():
return kb_id.strip()
if not kb_name or not kb_name.strip():
return None
resolved = await service.resolve_kb_reference(kb_name.strip())
if not resolved.get("success"):
raise HTTPException(status_code=404, detail=resolved)
return str(resolved["kb_id"])