yakub
feat: implement real-time source ingestion progress, chitchat intent classifier, and empty state fallback
fd1e711 | from fastapi import APIRouter, HTTPException | |
| from backend.database.connection import get_connection | |
| from backend.vectorstore import delete_vectors | |
| from pathlib import Path | |
| import os | |
| router = APIRouter() | |
| def get_sources(): | |
| """ | |
| GET /sources | |
| Returns all ingested sources for the frontend dashboard, | |
| knowledge sources page, and sidebar preview. | |
| """ | |
| with get_connection() as conn: | |
| cursor = conn.cursor(dictionary=True) | |
| cursor.execute(""" | |
| SELECT | |
| id, | |
| type, | |
| title, | |
| origin, | |
| language, | |
| chunk_count AS chunkCount, | |
| status, | |
| progress_percentage, | |
| error_message, | |
| created_at AS createdAt | |
| FROM sources | |
| ORDER BY created_at DESC | |
| """) | |
| sources = cursor.fetchall() | |
| # Convert datetime to ISO string for JSON serialization | |
| for s in sources: | |
| if s["createdAt"]: | |
| s["createdAt"] = s["createdAt"].isoformat() | |
| return {"sources": sources} | |
| def get_source_progress(source_id: str): | |
| """ | |
| GET /sources/{source_id} | |
| Returns details for a single source, including progress_percentage and error_message. | |
| """ | |
| with get_connection() as conn: | |
| cursor = conn.cursor(dictionary=True) | |
| cursor.execute(""" | |
| SELECT | |
| id, | |
| type, | |
| title, | |
| origin, | |
| language, | |
| chunk_count AS chunkCount, | |
| status, | |
| progress_percentage, | |
| error_message, | |
| created_at AS createdAt | |
| FROM sources | |
| WHERE id = %s | |
| """, (source_id,)) | |
| source = cursor.fetchone() | |
| if not source: | |
| raise HTTPException(status_code=404, detail="Source not found") | |
| # Convert datetime to ISO string for JSON serialization | |
| if source["createdAt"]: | |
| source["createdAt"] = source["createdAt"].isoformat() | |
| return source | |
| def delete_source(source_id: str): | |
| """ | |
| DELETE /sources/{id} | |
| Deletes a source and all its chunks from FAISS, MySQL, and disk. | |
| """ | |
| with get_connection() as conn: | |
| cursor = conn.cursor(dictionary=True) | |
| cursor.execute( | |
| "SELECT id, type, origin FROM sources WHERE id = %s", | |
| (source_id,) | |
| ) | |
| source = cursor.fetchone() | |
| if not source: | |
| raise HTTPException(status_code=404, detail="Source not found") | |
| cursor.execute( | |
| "SELECT id FROM chunks WHERE source_id = %s", | |
| (source_id,) | |
| ) | |
| chunk_rows = cursor.fetchall() | |
| chunk_ids = {row["id"] for row in chunk_rows} | |
| if chunk_ids: | |
| delete_vectors(chunk_ids) | |
| with get_connection() as conn: | |
| cursor = conn.cursor() | |
| cursor.execute("DELETE FROM sources WHERE id = %s", (source_id,)) | |
| conn.commit() | |
| if source["type"] == "pdf" and source["origin"]: | |
| pdf_path = Path(source["origin"]) | |
| if pdf_path.exists(): | |
| os.remove(pdf_path) | |
| print(f"[Sources] Deleted file: {pdf_path}") | |
| print(f"[Sources] Deleted source: {source_id} ({len(chunk_ids)} chunks)") | |
| return {"message": "Source deleted successfully", "id": source_id} |