File size: 6,058 Bytes
fd1e711 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | import shutil
import tempfile
import uuid
from pathlib import Path
from fastapi import APIRouter, UploadFile, File, HTTPException, BackgroundTasks
from pydantic import BaseModel
from backend.ingestion.pdf_loader import ingest_pdf
from backend.ingestion.url_loader import ingest_url
from backend.ingestion.youtube_loader import ingest_youtube
from backend.database.connection import get_connection
router = APIRouter()
# ββ Request models ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class UrlRequest(BaseModel):
url: str
language: str = "en"
class YoutubeRequest(BaseModel):
url: str
language: str = "en"
# ββ Background Task Wrappers ββββββββββββββββββββββββββββββββββββββββββββββββββ
def run_ingest_pdf_bg(tmp_path: Path, filename: str, source_id: str):
"""Safely runs PDF ingestion and always cleans up the temporary file."""
try:
ingest_pdf(tmp_path, filename, source_id)
except Exception as e:
print(f"[BG Ingest PDF] Critical failure: {e}")
finally:
if tmp_path.exists():
tmp_path.unlink()
print(f"[BG Ingest PDF] Cleaned up temporary file: {tmp_path}")
# ββ Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@router.post("/upload-pdf")
async def upload_pdf(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
"""
POST /upload-pdf
Accepts a PDF file upload, saves it temporarily,
starts ingestion in a background task, and immediately returns source ID.
"""
if not file.filename.endswith(".pdf"):
raise HTTPException(status_code=400, detail="Only PDF files are accepted.")
# Save uploaded file to a temp location first
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
shutil.copyfileobj(file.file, tmp)
tmp_path = Path(tmp.name)
source_id = str(uuid.uuid4())
dest_path = tmp_path # temporary placeholder path until moved in background task
try:
# Create initial pending source entry in MySQL
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO sources (id, type, title, origin, language, status, progress_percentage, chunk_count)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (source_id, "pdf", file.filename, str(dest_path), "en", "UPLOADING", 10, 0))
conn.commit()
# Dispatch background task
background_tasks.add_task(run_ingest_pdf_bg, tmp_path, file.filename, source_id)
except Exception as e:
if tmp_path.exists():
tmp_path.unlink()
raise HTTPException(status_code=500, detail=f"Database initialization failed: {str(e)}")
return {
"message" : "PDF upload initiated",
"source_id" : source_id,
"title" : file.filename,
"chunk_count": 0,
"status" : "UPLOADING"
}
@router.post("/add-url")
def add_url(req: UrlRequest, background_tasks: BackgroundTasks):
"""
POST /add-url
Accepts a website URL, scrapes and ingests it in the background.
"""
if not req.url.startswith("http"):
raise HTTPException(status_code=400, detail="Invalid URL. Must start with http:// or https://")
source_id = str(uuid.uuid4())
try:
# Create initial pending source entry in MySQL
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO sources (id, type, title, origin, language, status, progress_percentage, chunk_count)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (source_id, "url", req.url, req.url, "en", "EXTRACTING", 10, 0))
conn.commit()
# Dispatch background task
background_tasks.add_task(ingest_url, req.url, source_id)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database initialization failed: {str(e)}")
return {
"message" : "URL ingestion initiated",
"source_id" : source_id,
"title" : req.url,
"chunk_count": 0,
"status" : "EXTRACTING"
}
@router.post("/add-youtube")
def add_youtube(req: YoutubeRequest, background_tasks: BackgroundTasks):
"""
POST /add-youtube
Accepts a YouTube URL, fetches transcript and ingests it in the background.
"""
if "youtube.com" not in req.url and "youtu.be" not in req.url:
raise HTTPException(status_code=400, detail="Invalid YouTube URL.")
source_id = str(uuid.uuid4())
try:
# Create initial pending source entry in MySQL
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO sources (id, type, title, origin, language, status, progress_percentage, chunk_count)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (source_id, "youtube", req.url, req.url, "en", "EXTRACTING", 10, 0))
conn.commit()
# Dispatch background task
background_tasks.add_task(ingest_youtube, req.url, source_id)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database initialization failed: {str(e)}")
return {
"message" : "YouTube video ingestion initiated",
"source_id" : source_id,
"title" : req.url,
"chunk_count": 0,
"language" : req.language,
"status" : "EXTRACTING"
}
|