""" server_app.py — gr.Server entrypoint with carousel front page and API endpoints. Serves: GET / → HTML carousel (first slide embeds /access via iframe) GET /access → Mounted Color-UX-Access Gradio app (same as app.py) POST /api/generate_gallery → api_generate_gallery_from_bytes → JSON POST /api/analyze_vlm → api_analyze_cvd_grid_from_bytes → JSON POST /api/wcag_report → api_report_from_json → Markdown """ # GUARDRAIL: Do not add self-modifying or patch scripts. # Implement changes directly in this file or in helper modules with tests. # Root-level *fix*.py / apply_*.py files are prohibited. from __future__ import annotations import os import base64 from typing import List from fastapi import FastAPI from fastapi.responses import HTMLResponse import gradio as gr # Import the existing Gradio Blocks app from app.py (must not modify app.py) from app import demo # Import backend wrappers from backend_api import ( api_generate_gallery_from_bytes, api_analyze_cvd_grid_from_bytes, api_report_from_json, ) # ── Carousel HTML ───────────────────────────────────────────────────────────── _CAROUSEL_HTML = """ NARWALL Tech — Accessibility Suite

NARWALL Tech — Accessibility Suite

Multi-tool testing platform powered by AI

""" # ── gr.Server setup ──────────────────────────────────────────────────────────── # Create the Server — this is a FastAPI subclass with Gradio's API engine app = gr.Server(title="NARWALL Tech — Accessibility Suite") # Mount the existing Color-UX-Access Blocks app at /access # This serves the same UI as `uv run app.py` app.mount("/access", demo, name="color_ux_access") # ── Custom routes ───────────────────────────────────────────────────────────── @app.get("/") async def root(): """Serve the HTML carousel page at /.""" return HTMLResponse(content=_CAROUSEL_HTML, media_type="text/html") # ── API endpoints ───────────────────────────────────────────────────────────── async def _generate_gallery_handler(data: dict) -> dict: """Handler shared by @app.api and FastAPI POST /api/generate_gallery.""" b64 = data.get("image", "") if not b64: return {"error": "Missing 'image' field in request body", "items": []} try: image_bytes = base64.b64decode(b64) except Exception as e: return {"error": f"Could not decode base64 image: {e}", "items": []} try: gallery = api_generate_gallery_from_bytes(image_bytes) except ValueError as e: return {"error": str(e), "items": []} items = [] for img_bytes, label in gallery: items.append({ "image": base64.b64encode(img_bytes).decode("utf-8"), "label": label, }) return {"items": items} async def _analyze_vlm_handler(data: dict) -> dict: """Handler shared by @app.api and FastAPI POST /api/analyze_vlm.""" items = data.get("items", []) if not items: return {"error": "Empty gallery — upload an image first.", "findings": [], "passes": False} gallery = [] for item in items: try: img_bytes = base64.b64decode(item.get("image", "")) label = item.get("label", "Unknown") gallery.append((img_bytes, label)) except Exception: continue if not gallery: return {"error": "No valid images in gallery", "findings": [], "passes": False} return api_analyze_cvd_grid_from_bytes(gallery) # @app.api — Gradio client API (for programmatic callers via gradio_client) @app.api(name="generate_gallery") async def generate_gallery_endpoint(data: dict) -> dict: return await _generate_gallery_handler(data) @app.api(name="analyze_vlm") async def analyze_vlm_endpoint(data: dict) -> dict: return await _analyze_vlm_handler(data) @app.api(name="wcag_report") async def wcag_report_endpoint(data: dict) -> str: return api_report_from_json(data) # FastAPI routes — accessible via HTTP/TestClient at /api/* @app.post("/api/generate_gallery") async def fastapi_generate_gallery(data: dict) -> dict: """POST /api/generate_gallery — same as @app.api generate_gallery for HTTP clients.""" return await _generate_gallery_handler(data) @app.post("/api/analyze_vlm") async def fastapi_analyze_vlm(data: dict) -> dict: """POST /api/analyze_vlm — same as @app.api analyze_vlm for HTTP clients.""" return await _analyze_vlm_handler(data) @app.post("/api/wcag_report") async def fastapi_wcag_report(data: dict) -> str: """POST /api/wcag_report — same as @app.api wcag_report for HTTP clients.""" return api_report_from_json(data) # ── Local development entrypoint ────────────────────────────────────────────── if __name__ == "__main__": port = int(os.environ.get("PORT", "7860")) app.launch(server_name="0.0.0.0", server_port=port)