yushg's picture
Upload folder using huggingface_hub
a2d6a2a verified
Raw
History Blame Contribute Delete
4.77 kB
"""A zero-dependency local server for the densitygen viz.
Serves the baked bundle as static files and exposes ``POST /api/screen`` so the
"suggest a precursor" input can re-rank live. The endpoint runs the *same*
``screen()`` the CLI does -- the browser never scores anything itself, it only
renders what this returns. Pure stdlib (``http.server``) so ``ald-screen serve``
works with no extra installs.
"""
from __future__ import annotations
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from densitygen.schemas import ScreeningRequest
from densitygen.screen import screen
from densitygen.viz import response_to_payload, write_bundle
def _make_handler(bundle_dir: Path, api_url: str):
class Handler(BaseHTTPRequestHandler):
# Quieter logging; one line per request is enough for a demo server.
def log_message(self, fmt, *args): # noqa: A003
print("densitygen-serve:", fmt % args)
def _cors(self):
# REASON: the viz may be opened from file:// or a different port than
# the API; allow any origin so the live re-rank fetch isn't blocked.
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
def do_OPTIONS(self): # noqa: N802
self.send_response(204)
self._cors()
self.end_headers()
def _send_json(self, obj, status=200):
body = json.dumps(obj).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self._cors()
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_POST(self): # noqa: N802
if self.path.rstrip("/") != "/api/screen":
self._send_json({"error": "not found"}, 404)
return
try:
n = int(self.headers.get("Content-Length", 0))
payload = json.loads(self.rfile.read(n) or b"{}")
request = ScreeningRequest.model_validate(payload)
resp = screen(request)
out = response_to_payload(resp, request=request, mode="screen",
api_url=api_url)
self._send_json(out)
except Exception as e: # never 500 silently -- the UI shows this text
self._send_json({"error": f"{type(e).__name__}: {e}"}, 400)
def do_GET(self): # noqa: N802
# Static file serving rooted at the bundle dir. "/" -> the dc html.
rel = self.path.split("?", 1)[0].lstrip("/")
if rel in ("", "index.html"):
rel = "densitygen.dc.html"
target = (bundle_dir / rel).resolve()
# Path-traversal guard: never serve outside the bundle dir.
if bundle_dir.resolve() not in target.parents and target != bundle_dir.resolve():
self._send_json({"error": "forbidden"}, 403)
return
if not target.is_file():
self._send_json({"error": "not found"}, 404)
return
ctype = ("text/html" if target.suffix == ".html"
else "application/javascript" if target.suffix == ".js"
else "application/json" if target.suffix == ".json"
else "application/octet-stream")
data = target.read_bytes()
self.send_response(200)
self.send_header("Content-Type", ctype)
self._cors()
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
return Handler
def serve(request: ScreeningRequest, *, host: str = "127.0.0.1", port: int = 8765,
bundle_dir: str | Path | None = None) -> None:
"""Bake a bundle for `request`, then serve it with a live /api/screen."""
api_url = f"http://{host}:{port}"
out = Path(bundle_dir or "densitygen_viz")
resp = screen(request)
payload = response_to_payload(resp, request=request, mode="screen", api_url=api_url)
entry = write_bundle(payload, out)
handler = _make_handler(out, api_url)
httpd = ThreadingHTTPServer((host, port), handler)
print(f"densitygen viz live at {api_url}/ (bundle: {entry})")
print("suggest a precursor in the right rail -> live re-rank via POST /api/screen")
print("Ctrl-C to stop.")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nstopped.")
httpd.shutdown()