import os import uuid from fastapi import FastAPI, UploadFile, File, Request from fastapi.responses import HTMLResponse, JSONResponse, FileResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from fastapi.middleware.cors import CORSMiddleware from dotenv import load_dotenv from langgraph.graph import StateGraph from playwright_model import generate_ui_report from agents.graph_builder_langgraph import graph # Import the compiled agent pipeline # ========================================================== # 🔧 Setup # ========================================================== load_dotenv("settings.env") app = FastAPI(title="PowerApps Mockup Generator") # Enable CORS (allow all for testing, restrict in production) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Paths BASE_DIR = os.path.dirname(os.path.abspath(__file__)) UPLOAD_FOLDER = os.path.join(BASE_DIR, "static", "uploads") OUTPUT_FOLDER = os.path.join(BASE_DIR, "static", "outputs") os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(OUTPUT_FOLDER, exist_ok=True) # Static + templates app.mount("/static", StaticFiles(directory="static"), name="static") templates = Jinja2Templates(directory="templates") # ========================================================== # 🏠 Root Route # ========================================================== @app.get("/", response_class=HTMLResponse) async def index(request: Request): return templates.TemplateResponse("index.html", {"request": request}) # ========================================================== # 📄 File Upload → Agent Pipeline (UI Generation) # ========================================================== import traceback @app.post("/upload") async def upload_files(request: Request, files: list[UploadFile] = File(...)): try: file_paths = [] for file in files: filename = f"{uuid.uuid4()}_{file.filename}" filepath = os.path.join(UPLOAD_FOLDER, filename) with open(filepath, "wb") as f: f.write(await file.read()) file_paths.append(filepath) print("🚀 Running document → requirements → UI generation pipeline...") result = await graph.ainvoke({"files": file_paths}) # Convert to dict if needed if hasattr(result, "dict"): result = result.dict() generated_html = result.get("html") public_url = result.get("public_url") print("✅ Final graph result:", result) if not generated_html: return JSONResponse({"error": "UI generation failed — empty HTML returned."}, status_code=500) return JSONResponse({"html": generated_html, "link": public_url}) except Exception as e: print("❌ Full pipeline error trace:") traceback.print_exc() return JSONResponse({"error": f"AI Pipeline failed: {str(e)}"}, status_code=500) # ========================================================== # 📘 Generate PDF Report (Triggered by Button) # ========================================================== @app.post("/generate_report") async def generate_report(request: Request): """Generate UI walkthrough report using Playwright after UI is visible.""" data = await request.json() app_url = data.get("url") if not app_url: return JSONResponse({"error": "No URL provided"}, status_code=400) output_pdf = os.path.join(OUTPUT_FOLDER, "UI_Report.pdf") try: await generate_ui_report(app_url, output_pdf) return FileResponse(output_pdf, filename="UI_Report.pdf", media_type="application/pdf") except Exception as e: return JSONResponse({"error": f"PDF generation failed: {str(e)}"}, status_code=500) import os import uuid import re from fastapi import FastAPI, UploadFile, File, Request from fastapi.responses import HTMLResponse, JSONResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from fastapi.middleware.cors import CORSMiddleware from dotenv import load_dotenv from pdfminer.high_level import extract_text as pdf_extract_text from docx import Document import google.generativeai as genai import fpdf # ========================================================== # 🔧 Setup # ========================================================== load_dotenv("settings.env") app = FastAPI(title="PowerApps Mockup Generator") # Enable CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], # ⚠️ Restrict in production allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Directory setup BASE_DIR = os.path.dirname(os.path.abspath(__file__)) UPLOAD_FOLDER = os.path.join(BASE_DIR, "static", "uploads") OUTPUT_FOLDER = os.path.join(BASE_DIR, "static", "outputs") os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(OUTPUT_FOLDER, exist_ok=True) # Serve static files app.mount("/static", StaticFiles(directory="static"), name="static") # Setup templates templates = Jinja2Templates(directory="templates") # Gemini setup genai.configure(api_key=os.environ.get("GEMINI_API_KEY2")) model = genai.GenerativeModel("gemini-2.5-pro") # ========================================================== # 📄 Helper Function # ========================================================== def extract_text_from_file(filepath: str) -> str: """Extract text from a PDF or DOCX file.""" if filepath.endswith(".pdf"): return pdf_extract_text(filepath) elif filepath.endswith(".docx"): doc = Document(filepath) return "\n".join([p.text for p in doc.paragraphs]) return "" # ========================================================== # 🧭 Routes # ========================================================== @app.get("/", response_class=HTMLResponse) async def index(request: Request): """Serve the main UI.""" return templates.TemplateResponse("index.html", {"request": request}) @app.post("/upload") async def upload_files(request: Request, files: list[UploadFile] = File(...)): """Handle uploaded files and generate PowerApps-style HTML.""" all_text = "" for file in files: filename = f"{uuid.uuid4()}_{file.filename}" filepath = os.path.join(UPLOAD_FOLDER, filename) with open(filepath, "wb") as f: f.write(await file.read()) extracted_text = extract_text_from_file(filepath) all_text += f"\n\nDocument: {file.filename}\n{extracted_text}" if not all_text.strip(): return JSONResponse({"error": "No readable content found in files."}, status_code=400) reference_path = os.path.join(BASE_DIR, "templates", "demo_qms_design.html") if not os.path.exists(reference_path): return JSONResponse({"error": "Reference design not found."}, status_code=500) with open(reference_path, "r", encoding="utf-8") as f: reference_html = f.read() # ========================================================== # 🧠 Gemini Prompt # ========================================================== prompt = f""" You are an expert PowerApps-style UI generator and business workflow designer. Your goal is to create a Recruitment Management Application HTML layout that follows **exactly the same layout, structure, and JavaScript logic** as the reference QMS app design provided below. ### 🔧 INSTRUCTIONS 1️⃣ **Mimic the UI structure and components** of the QMS reference exactly: - Keep the **sidebar**, **header**, and **multi-screen logic** identical. - Each .menu-item must open a corresponding .screen using the same showScreen() JS pattern. - Each workflow stage (like Draft → Manage → Review → etc.) should be clickable and update the workflow progress using the same logic from the reference. 2️⃣ **Adapt the content** to Recruitment Workflow: Replace QMS-specific names with Recruitment equivalents while keeping UI consistency. For example: - “QMS Request” → “Recruitment Request” - “Draft / Review / Approval / Published” → “Requisition / Sourcing / Interview / Offer / Hired” - “Document Type / Identifier” → “Job Role / Candidate ID” - “Reviewer / Approver” → “Hiring Manager / HR Lead” 3️⃣ **Keep CSS and JS identical** to the reference except where labels or icons differ. - Preserve all existing class names (e.g., .workflow-step, .screen, .menu-item, .kanban-board). - Only modify inner HTML and labels. - Keep inline scripts functional — do not remove event handlers. 4️⃣ **Functional Requirements** - Must be a single standalone HTML document (no markdown, no external JS). - Include embedded