| 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)
|
|
|
|
|
| import time
|
| try:
|
| while True:
|
| time.sleep(1)
|
| except KeyboardInterrupt:
|
| print("Shutting down.")
|
|
|
|
|
| if __name__ == "__main__":
|
| launch()
|
|
|