""" 知识库 API — 上传 / 列表 / 删除 / 来源筛选 / 语义搜索 """ from typing import Optional from pydantic import BaseModel from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query, status from sqlmodel import Session from app.core.deps import get_current_user, require_role from app.db.session import get_session from app.models.user import User, UserRole from app.services.knowledge_service import KnowledgeService router = APIRouter(prefix="/api/knowledge", tags=["知识库"]) @router.post("/upload") async def upload_md_files( files: list[UploadFile] = File(...), session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian)), ): """ 上传单个或多个 .md 文件,解析 frontmatter,写入知识库(含向量)。 仅制片人/责编可用。 """ svc = KnowledgeService() results = [] errors = [] for f in files: if not f.filename.endswith(".md"): errors.append({"file": f.filename, "error": "仅支持 .md 文件"}) continue try: content = await f.read() item = svc.store_md_file(content, f.filename) results.append({ "id": item.id, "title": item.title, "source_type": item.source_type, }) except Exception as e: errors.append({"file": f.filename, "error": str(e)}) return {"uploaded": results, "errors": errors} @router.get("/items") def list_knowledge_items( source_type: Optional[str] = Query(None), session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian, UserRole.biandao)), ): """ 知识库列表,支持按 source_type 筛选。三角色均可读。 """ svc = KnowledgeService() items = svc.list_items(source_type=source_type) return items @router.delete("/items/{item_id}") def delete_knowledge_item( item_id: int, session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian)), ): """ 删除知识库条目(级联删除向量)。仅制片人/责编可用。 """ svc = KnowledgeService() deleted = svc.delete_item(item_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="条目不存在") return {"message": "删除成功"} @router.get("/sources") def list_distinct_sources( session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian, UserRole.biandao)), ): """ 返回库里所有不重复的具体出处(source_detail),供来源筛选下拉动态生成。 三角色均可读。 """ svc = KnowledgeService() sources = svc.get_distinct_sources() return [{"source": s} for s in sources] @router.get("/grouped") def get_grouped_knowledge_items( session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian, UserRole.biandao)), ): """ 返回按 source_type → source_detail 两层聚合的树形结构, 含「全部」根节点,供知识库树形导航使用。 三角色均可读。 """ svc = KnowledgeService() return svc.get_grouped_items() class SearchRequest(BaseModel): query: str top_k: int = 5 @router.post("/search") def search_knowledge( body: SearchRequest, session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian, UserRole.biandao)), ): """ 语义检索:输入一段文字,返回最相关的知识库条目及相似度。 查询向量用 type="query"(区分于存入时的 type="db")。 三角色均可读。 """ svc = KnowledgeService() results = svc.search_similar(query_text=body.query, top_k=body.top_k) return { "results": results, "query": body.query, "count": len(results), }