infoshield / app.py
Pavle-17's picture
Upload 14 files
91eb6bc verified
Raw
History Blame Contribute Delete
4.32 kB
"""
InfoShield — Hugging Face Space entry point (Gradio SDK, no Gradio UI).
This Space is declared `sdk: gradio` (see README.md) so it builds and runs, but
the user-facing surface is the InfoShield React site — there is NO Gradio
interface. A minimal, hidden Gradio app is mounted only at /_gradio to satisfy
the Gradio SDK runtime; nothing links to it.
Flat layout note: Hugging Face's web uploader cannot preserve subfolders, so all
files (the React build + cache data + this script) live in the repo root. This
file serves the static site from its own directory and reads the cache from the
same place — cache-first, no torch, no Gemini, no network.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import gradio as gr
from fastapi import FastAPI
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
ROOT = Path(__file__).parent.resolve()
PORT = int(os.environ.get("PORT", os.environ.get("GRADIO_SERVER_PORT", 7860)))
def _load_json(name: str, default):
try:
return json.loads((ROOT / name).read_text(encoding="utf-8"))
except Exception:
return default
_POSTS = _load_json("posts.json", [])
_CACHE = _load_json("cache.json", {"meta": {}, "posts": {}})
_CACHE_POSTS = _CACHE.get("posts", {})
def _merged_feed():
out = []
for p in _POSTS:
c = _CACHE_POSTS.get(p["id"], {})
cls = c.get("classification", {}) or {}
out.append({**p, "classification": cls, "analysis": c.get("analysis"), "flagged": bool(cls.get("flagged"))})
return out
def _stats():
feed = _merged_feed()
flagged = [p for p in feed if p["flagged"]]
techniques, confidences = {}, []
for p in flagged:
if p["classification"].get("confidence") is not None:
confidences.append(p["classification"]["confidence"])
for t in ((p.get("analysis") or {}).get("techniques") or []):
name = t.get("name") if isinstance(t, dict) else t
if name:
techniques[name] = techniques.get(name, 0) + 1
avg = round(sum(confidences) / len(confidences), 4) if confidences else 0.0
return {
"total_posts": len(feed),
"flagged_posts": len(flagged),
"clear_posts": len(feed) - len(flagged),
"technique_distribution": techniques,
"avg_confidence": avg,
"model_meta": _CACHE.get("meta", {}),
"mode": "cache-first (read-only, no model)",
}
_ACTIVATION = {"target_url": "", "enabled": False}
app = FastAPI(title="InfoShield (cache-first, static)", docs_url=None, redoc_url=None)
@app.get("/health")
def health():
return {"status": "ok", "mode": "cache-first", "model_loaded": False,
"device": "none (serving cache)", "posts": len(_POSTS)}
@app.get("/feed")
def feed():
return {"posts": _merged_feed()}
@app.get("/stats")
def stats():
return _stats()
@app.get("/post-analysis/{post_id}")
def post_analysis(post_id: str):
c = _CACHE_POSTS.get(post_id)
if not c:
return JSONResponse({"error": "unknown post id"}, status_code=404)
return {"id": post_id, "classification": c.get("classification", {}), "analysis": c.get("analysis")}
@app.get("/activation-target")
def get_activation_target():
return _ACTIVATION
@app.post("/activation-target")
def set_activation_target(payload: dict):
_ACTIVATION["target_url"] = str(payload.get("target_url", ""))
_ACTIVATION["enabled"] = bool(payload.get("enabled", True))
return _ACTIVATION
# Hidden Gradio app — present only to satisfy the Gradio SDK runtime. Mounted
# before the static catch-all so its routes resolve first.
with gr.Blocks(analytics_enabled=False, title="InfoShield (internal)") as _hidden:
gr.Markdown("InfoShield runs as a static site at `/`. This Gradio mount is internal.")
app = gr.mount_gradio_app(app, _hidden, path="/_gradio")
# Serve index.html for "/" explicitly, then mount the flat static dir LAST so the
# API routes and /_gradio above take precedence over the "/" catch-all.
@app.get("/")
def _index():
return FileResponse(ROOT / "index.html")
app.mount("/", StaticFiles(directory=str(ROOT), html=True), name="static")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=PORT)