Spaces:
Sleeping
Sleeping
| """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 | |
| 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)} | |
| 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)} | |
| 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"]} | |
| 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"]) | |
| 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 | |
| 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"'}) | |
| def list_cases(): | |
| """Past-case history (persisted across restarts).""" | |
| return {"cases": store.list_cases()} | |
| 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) | |