from __future__ import annotations import sys, os, threading, webbrowser, http.server, json from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from src.core.command_router import CommandRouter from src.core.models import JobConfig, PDFOperation from src.core.orchestrator import OfflinePDFOrchestrator UI_HTML = Path(__file__).parent / "web" / "orchestrator.html" PORT = 18472 COMMAND_ROUTER = CommandRouter(project_root=Path(__file__).parent.parent.parent) class _Handler(http.server.SimpleHTTPRequestHandler): def __init__(self, *a, **kw): super().__init__(*a, directory=str(UI_HTML.parent), **kw) def log_message(self, *_): pass def do_POST(self): if self.path == "/run": length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(length) payload = json.loads(body) result = _run_job(payload) resp = json.dumps(result).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(resp))) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(resp) elif self.path == "/team-plan": length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(length) payload = json.loads(body) result = COMMAND_ROUTER.plan_command(payload) resp = json.dumps(result).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(resp))) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(resp) else: self.send_response(404); self.end_headers() def do_GET(self): if self.path == "/team-config": result = { "success": True, "team": COMMAND_ROUTER.describe_team(), "available_pipelines": COMMAND_ROUTER.orchestrator.list_available_pipelines(), } resp = json.dumps(result).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(resp))) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(resp) return super().do_GET() def do_OPTIONS(self): self.send_response(204) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET,POST,OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") self.end_headers() def _run_job(payload: dict) -> dict: try: ops = [] for item in payload.get("operations", []): ops.append(PDFOperation(**item)) input_pdf = Path(payload.get("input_pdf", "")) output_dir = Path(payload.get("output_dir", Path.home() / "pdf-output")) if not input_pdf.exists(): return {"success": False, "error": f"Input PDF not found: {input_pdf}"} cfg = JobConfig( input_pdf = input_pdf, output_dir = output_dir, run_ocr = payload.get("force_ocr", False), force_ocr = payload.get("force_ocr", False), ocr_language = payload.get("ocr_language", "eng"), operations = ops, ) orc = OfflinePDFOrchestrator(output_dir / "_jobs") result = orc.run(cfg) return { "success": result.success, "job_id": result.job_id, "final_pdf": str(result.final_pdf) if result.final_pdf else "", "log_file": str(result.log_file), "stages": result.stages, "errors": result.errors, } except Exception as exc: return {"success": False, "error": str(exc)} def _start_server(): server = http.server.HTTPServer(("127.0.0.1", PORT), _Handler) server.serve_forever() def launch(): t = threading.Thread(target=_start_server, daemon=True) t.start() url = f"http://127.0.0.1:{PORT}/orchestrator.html" print(f"Opening PDF Orchestrator UI at {url}") webbrowser.open(url) # Keep process alive import time try: while True: time.sleep(1) except KeyboardInterrupt: print("Shutting down.") if __name__ == "__main__": launch()