Plaiglab / webapp /server.py
SanidhyaDhangar's picture
PlaigLab — Hugging Face Space (Docker) clean deploy
ebebfe8
Raw
History Blame Contribute Delete
5.07 kB
"""PlaigLab web app: upload a paper -> live forensic analysis against world
literature -> similarity + AI report. Run: python webapp/server.py
"""
import os
import sys
import threading
import traceback
import uuid
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.responses import JSONResponse, Response
from fastapi.staticfiles import StaticFiles
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)
from plagdetect import store # noqa: E402
from plagdetect.webpipeline import analyze_document # noqa: E402
UPLOAD_DIR = os.path.join(ROOT, "data", "uploads")
STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
ALLOWED = (".pdf", ".txt", ".docx")
app = FastAPI(title="PlaigLab")
JOBS = {}
def _run_job(job_id, path, depth):
job = JOBS[job_id]
def prog(msg):
job["log"].append(msg)
try:
job["status"] = "running"
job["result"] = analyze_document(path, progress=prog, depth=depth)
job["status"] = "done"
try:
store.save_case(job["result"]) # persist for case history
except Exception:
traceback.print_exc()
except Exception as exc: # surfaced to the UI
traceback.print_exc()
job["status"] = "error"
job["error"] = str(exc)
def _start(path, filename, depth="deep"):
job_id = uuid.uuid4().hex[:12]
JOBS[job_id] = {"status": "queued", "log": [], "result": None,
"error": None, "filename": filename}
threading.Thread(target=_run_job, args=(job_id, path, depth),
daemon=True).start()
return job_id
@app.post("/api/analyze")
async def analyze(file: UploadFile = File(...), depth: str = "deep"):
ext = os.path.splitext(file.filename or "")[1].lower()
if ext not in ALLOWED:
raise HTTPException(400, f"Only {', '.join(ALLOWED)} files are supported")
os.makedirs(UPLOAD_DIR, exist_ok=True)
job_id = uuid.uuid4().hex[:6]
dest = os.path.join(UPLOAD_DIR, f"{job_id}_{os.path.basename(file.filename)}")
with open(dest, "wb") as f:
f.write(await file.read())
return {"job_id": _start(dest, file.filename, depth)}
@app.post("/api/sample")
def sample(depth: str = "deep"):
path = os.path.join(ROOT, "data", "test_real.txt")
if not os.path.exists(path):
raise HTTPException(404, "sample document missing")
return {"job_id": _start(path, "sample_paper.txt", depth)}
@app.get("/api/jobs/{job_id}")
def job_status(job_id: str):
job = JOBS.get(job_id)
if not job:
raise HTTPException(404, "unknown job")
return {"status": job["status"], "log": job["log"],
"error": job["error"], "filename": job["filename"]}
@app.get("/api/jobs/{job_id}/report")
def job_report(job_id: str):
job = JOBS.get(job_id)
if not job or job["status"] != "done":
raise HTTPException(404, "report not ready")
return JSONResponse(job["result"])
@app.get("/api/jobs/{job_id}/manifest")
def job_manifest(job_id: str):
"""Downloadable coverage manifest (R2 — 'kya kya scrape kiya' full list)."""
job = JOBS.get(job_id)
if not job or job["status"] != "done":
raise HTTPException(404, "manifest not ready")
r = job["result"]
payload = {"case_id": r["case_id"], "filename": r["filename"],
"title": r["title"], "verdict": r["verdict"],
"similarity_index": r["similarity_index"],
"ai_score": r["ai_score"], "coverage": r["coverage"]}
return JSONResponse(payload, headers={
"Content-Disposition":
f'attachment; filename="manifest_{r["case_id"]}.json"'})
def _result_for(job_id):
job = JOBS.get(job_id)
if job and job["status"] == "done":
return job["result"]
return store.get_case(job_id) # fall back to persisted history
@app.get("/api/jobs/{job_id}/pdf")
def job_pdf(job_id: str):
"""Downloadable forensic PDF report."""
r = _result_for(job_id)
if not r:
raise HTTPException(404, "report not ready")
from plagdetect.pdfreport import build_pdf
pdf = build_pdf(r)
return Response(pdf, media_type="application/pdf", headers={
"Content-Disposition":
f'attachment; filename="plaiglab_{r.get("case_id", job_id)}.pdf"'})
@app.get("/api/cases")
def list_cases():
"""Past-case history (persisted across restarts)."""
return {"cases": store.list_cases()}
@app.get("/api/cases/{case_id}")
def get_case(case_id: str):
r = store.get_case(case_id)
if not r:
raise HTTPException(404, "unknown case")
return JSONResponse(r)
app.mount("/", StaticFiles(directory=STATIC_DIR, html=True), name="static")
if __name__ == "__main__":
import uvicorn
# Host/port are env-driven so the same entrypoint works locally and on
# container hosts (Hugging Face Spaces routes to 0.0.0.0:7860).
host = os.environ.get("HOST", "127.0.0.1")
port = int(os.environ.get("PORT", "8230"))
uvicorn.run(app, host=host, port=port)