Download backend/app/api/endpoints/documents.py from Ramosh99/cognify: direct link, hf CLI and curl.
- Browser
- Download file 5 kB
-
https://huggingface.co/spaces/Ramosh99/cognify/resolve/main/backend/app/api/endpoints/documents.py
- Command line
-
hf download hf://spaces/Ramosh99/cognify/backend/app/api/endpoints/documents.py
-
curl -L -o documents.py https://huggingface.co/spaces/Ramosh99/cognify/resolve/main/backend/app/api/endpoints/documents.py
5 kB
| """ | |
| Documents API endpoints | |
| ----------------------- | |
| Handles document ingestion (upload + ingest into vector DB) | |
| and semantic search (retrieve relevant chunks for a query). | |
| All operations are scoped to the authenticated user — users can only | |
| ingest and search their own documents. | |
| """ | |
| from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, status | |
| from pydantic import BaseModel | |
| from typing import List, Optional | |
| import io | |
| from app.core.auth import get_current_user_id | |
| from app.services.rag_service import rag_service | |
| router = APIRouter(prefix="/documents", tags=["Documents"]) | |
| # ------------------------------------------------------------------ # | |
| # Request / Response models | |
| # ------------------------------------------------------------------ # | |
| class IngestTextRequest(BaseModel): | |
| text: str | |
| topic: Optional[str] = None | |
| source: Optional[str] = None | |
| class IngestResponse(BaseModel): | |
| message: str | |
| chunks_stored: int | |
| class SearchRequest(BaseModel): | |
| query: str | |
| top_k: int = 5 | |
| filter_topic: Optional[str] = None | |
| class SearchResult(BaseModel): | |
| text: str | |
| score: float | |
| class SearchResponse(BaseModel): | |
| query: str | |
| results: List[SearchResult] | |
| # ------------------------------------------------------------------ # | |
| # Endpoints | |
| # ------------------------------------------------------------------ # | |
| def ingest_text( | |
| body: IngestTextRequest, | |
| user_id: str = Depends(get_current_user_id), | |
| ): | |
| """ | |
| Ingest raw text directly (useful for pasting notes or lecture content). | |
| Data is stored under the authenticated user's account only. | |
| """ | |
| metadata = {} | |
| if body.topic: | |
| metadata["topic"] = body.topic | |
| if body.source: | |
| metadata["source"] = body.source | |
| count = rag_service.ingest(body.text, user_id=user_id, metadata=metadata) | |
| return IngestResponse(message="Text ingested successfully.", chunks_stored=count) | |
| async def ingest_file( | |
| file: UploadFile = File(...), | |
| topic: Optional[str] = None, | |
| user_id: str = Depends(get_current_user_id), | |
| ): | |
| """ | |
| Upload a plain text (.txt) or PDF file to be chunked and embedded. | |
| Data is stored under the authenticated user's account only. | |
| """ | |
| if not file.filename: | |
| raise HTTPException(status_code=400, detail="No file provided.") | |
| content = await file.read() | |
| if file.filename.endswith(".txt"): | |
| text = content.decode("utf-8", errors="ignore") | |
| elif file.filename.endswith(".pdf"): | |
| try: | |
| from pypdf import PdfReader | |
| reader = PdfReader(io.BytesIO(content)) | |
| text = "\n".join(page.extract_text() or "" for page in reader.pages) | |
| except Exception as e: | |
| raise HTTPException(status_code=422, detail=f"Could not parse PDF: {e}") | |
| else: | |
| raise HTTPException(status_code=415, detail="Only .txt and .pdf files are supported.") | |
| metadata = {"source": file.filename} | |
| if topic: | |
| metadata["topic"] = topic | |
| count = rag_service.ingest(text, user_id=user_id, metadata=metadata) | |
| return IngestResponse(message=f"File '{file.filename}' ingested successfully.", chunks_stored=count) | |
| def search_documents( | |
| body: SearchRequest, | |
| user_id: str = Depends(get_current_user_id), | |
| ): | |
| """ | |
| Semantic search: returns the top-k most relevant document chunks for a query. | |
| Only searches the authenticated user's own documents. | |
| """ | |
| if not body.query.strip(): | |
| raise HTTPException(status_code=400, detail="Query must not be empty.") | |
| raw_results = rag_service.retrieve( | |
| query=body.query, | |
| user_id=user_id, | |
| top_k=body.top_k, | |
| filter_topic=body.filter_topic, | |
| ) | |
| results = [SearchResult(text=r["text"], score=r["score"]) for r in raw_results] | |
| return SearchResponse(query=body.query, results=results) | |
| def get_document_stats(user_id: str = Depends(get_current_user_id)): | |
| """Returns document count and chunk count for the authenticated user.""" | |
| try: | |
| from app.core.supabase import get_supabase_client | |
| client = get_supabase_client() | |
| res = ( | |
| client.table("documents") | |
| .select("source, doc_title", count="exact") | |
| .eq("user_id", user_id) | |
| .execute() | |
| ) | |
| total_chunks = res.count or 0 | |
| sources = set() | |
| for row in res.data or []: | |
| s = row.get("source") or row.get("doc_title") | |
| if s: | |
| sources.add(s) | |
| doc_count = len(sources) if sources else (1 if total_chunks > 0 else 0) | |
| return {"document_count": doc_count, "chunk_count": total_chunks} | |
| except Exception as e: | |
| return {"document_count": 0, "chunk_count": 0, "error": str(e)} | |