image1 / app.py
dina1's picture
Update app.py
0a0cafc verified
# =========================================================
# Intelligent PowerApps Mockup Generator
# From Business Requirement PDF → Multi-Screen PowerApps-Style Mockup Images
# =========================================================
import google.generativeai as genai
import gradio as gr
from html2image import Html2Image
import pdfplumber
import uuid
import os
import re
import time
# -----------------------
# Configure Gemini API
# -----------------------
genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
model = genai.GenerativeModel("gemini-2.5-pro")
# -----------------------
# Html2Image Setup
# -----------------------
hti = Html2Image(browser_executable="/usr/bin/chromium")
# -----------------------
# Enhanced Base Layout Template with PowerApps Styling
# -----------------------
BASE_TEMPLATE = """
<html>
<head>
<style>
:root {
--primary-blue: #0078d4;
--primary-dark: #005a9e;
--sidebar-bg: #2d2d2d;
--sidebar-hover: #3a3a3a;
--sidebar-active: #0078d4;
--card-bg: #ffffff;
--card-shadow: 0 2px 8px rgba(0,0,0,0.1);
--border-radius: 4px;
--text-dark: #323130;
--text-light: #605e5c;
--success: #107c10;
--warning: #d83b01;
--background: #f5f5f5;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', 'Segoe UI Web (West European)', sans-serif;
background: var(--background);
color: var(--text-dark);
line-height: 1.4;
}
.app-container {
display: flex;
min-height: 100vh;
}
/* Sidebar Styles */
.sidebar {
width: 240px;
background: var(--sidebar-bg);
color: white;
display: flex;
flex-direction: column;
}
.sidebar-header {
display: flex;
align-items: center;
padding: 16px;
font-size: 16px;
font-weight: 600;
background: #1f1f1f;
border-bottom: 1px solid #444;
}
.sidebar-header svg {
margin-right: 12px;
}
.sidebar-nav {
flex: 1;
padding: 8px 0;
}
.sidebar-section {
padding: 8px 0;
border-bottom: 1px solid #444;
}
.section-label {
padding: 8px 16px;
font-size: 12px;
font-weight: 600;
color: #aaa;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.sidebar-item {
display: flex;
align-items: center;
padding: 10px 16px;
color: #ccc;
text-decoration: none;
transition: all 0.2s ease;
cursor: pointer;
}
.sidebar-item:hover {
background: var(--sidebar-hover);
color: white;
}
.sidebar-item.active {
background: var(--sidebar-active);
color: white;
}
.sidebar-item svg {
margin-right: 12px;
width: 16px;
height: 16px;
}
/* Main Content Styles */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
}
.topbar {
height: 52px;
background: white;
border-bottom: 1px solid #e1e1e1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
}
.topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
.topbar-title {
font-size: 18px;
font-weight: 600;
color: var(--text-dark);
}
.topbar-right {
display: flex;
align-items: center;
gap: 12px;
}
.topbar-icon {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.2s ease;
}
.topbar-icon:hover {
background: #f0f0f0;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
}
.user-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--primary-blue);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: 600;
}
/* Content Area */
.content-area {
flex: 1;
padding: 20px;
overflow-y: auto;
}
/* PowerApps Card Components */
.card {
background: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--card-shadow);
margin-bottom: 16px;
border: 1px solid #e1e1e1;
}
.card-header {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: between;
align-items: center;
}
.card-title {
font-size: 16px;
font-weight: 600;
color: var(--text-dark);
}
.card-actions {
display: flex;
gap: 8px;
}
.card-body {
padding: 20px;
}
.card-footer {
padding: 12px 20px;
border-top: 1px solid #f0f0f0;
background: #fafafa;
}
/* Grid System */
.grid {
display: grid;
gap: 16px;
}
.grid-2 {
grid-template-columns: 1fr 1fr;
}
.grid-3 {
grid-template-columns: 1fr 1fr 1fr;
}
.grid-4 {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
/* Buttons */
.btn {
padding: 8px 16px;
border: none;
border-radius: var(--border-radius);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn-primary {
background: var(--primary-blue);
color: white;
}
.btn-primary:hover {
background: var(--primary-dark);
}
.btn-secondary {
background: #f0f0f0;
color: var(--text-dark);
border: 1px solid #d1d1d1;
}
.btn-secondary:hover {
background: #e5e5e5;
}
.btn-success {
background: var(--success);
color: white;
}
.btn-warning {
background: var(--warning);
color: white;
}
/* Tables */
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #f0f0f0;
}
.table th {
background: #fafafa;
font-weight: 600;
color: var(--text-dark);
font-size: 14px;
}
.table td {
color: var(--text-light);
font-size: 14px;
}
.table tr:hover {
background: #f8f8f8;
}
/* Form Elements */
.form-group {
margin-bottom: 16px;
}
.form-label {
display: block;
margin-bottom: 6px;
font-weight: 600;
color: var(--text-dark);
font-size: 14px;
}
.form-control {
width: 100%;
padding: 8px 12px;
border: 1px solid #d1d1d1;
border-radius: var(--border-radius);
font-size: 14px;
transition: border 0.2s ease;
}
.form-control:focus {
outline: none;
border-color: var(--primary-blue);
box-shadow: 0 0 0 1px var(--primary-blue);
}
/* Status Indicators */
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
display: inline-block;
}
.status-success {
background: #dff6dd;
color: var(--success);
}
.status-warning {
background: #fff4ce;
color: #8a6500;
}
.status-error {
background: #fde7e9;
color: #a80000;
}
.status-info {
background: #deecf9;
color: var(--primary-blue);
}
/* Quick Action Tiles */
.tile-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.tile {
background: white;
border-radius: var(--border-radius);
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid #e1e1e1;
}
.tile:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.tile-icon {
width: 48px;
height: 48px;
margin: 0 auto 12px;
background: var(--primary-blue);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.tile-title {
font-weight: 600;
margin-bottom: 4px;
color: var(--text-dark);
}
.tile-description {
font-size: 12px;
color: var(--text-light);
}
/* Activity Feed */
.activity-item {
display: flex;
align-items: flex-start;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--primary-blue);
margin-right: 12px;
margin-top: 6px;
flex-shrink: 0;
}
.activity-content {
flex: 1;
}
.activity-text {
margin-bottom: 4px;
color: var(--text-dark);
}
.activity-time {
font-size: 12px;
color: var(--text-light);
}
</style>
</head>
<body>
<div class="app-container">
{user_sidebar}
<div class="main-content">
{user_topbar}
<div class="content-area">
{user_content}
</div>
</div>
</div>
</body>
</html>
"""
# -----------------------
# Enhanced SVG Icons for PowerApps
# -----------------------
SVG_FLUENT = {
"hamburger": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z"/></svg>""",
"home": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>""",
"recent": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 8v5l4 2 .7-1.2-3.2-1.8V8h-1.5zM12 2a10 10 0 00-10 10H0l4 4 4-4H5a7 7 0 117 7 7 7 0 01-7-7H3a9 9 0 109-9z"/></svg>""",
"pinned": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M14 2v2l2 2v3l2 2v2H6v-2l2-2V6l2-2V2h4zm-1 13v7h-2v-7h2z"/></svg>""",
"business": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z"/></svg>""",
"archive": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0 4h2v-2H3v2zm0 4h18V5H3v16zM19 7v10H5V7h14z"/></svg>""",
"workflow": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M17 12h-5v5h5v-5zm-5-2h5V5h-5v5zm2-3h1v1h-1V7zm0 5h1v1h-1v-1zm-8 5h5v-5H6v5zm0-7h5V5H6v5zm2-3h1v1H8V7zm0 5h1v1H8v-1zM3 3h18v18H3V3zm16 16V5H5v14h14z"/></svg>""",
"search": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>""",
"settings": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.488.488 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>""",
"users": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M16 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm4 18v-6h2.5l-2.54-7.63A2.01 2.01 0 0018.06 7h-2.12c-.93 0-1.76.55-2.13 1.33l-.19.46-3.53 1.29c-.45.17-.75.61-.75 1.1v7.82h2V14h3v8h3zm-7.5-10.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5S11 9.17 11 10s.67 1.5 1.5 1.5zM5.5 6c1.11 0 2-.89 2-2s-.89-2-2-2-2 .89-2 2 .89 2 2 2zm2 16v-7H5v7h2.5zm2-16c1.11 0 2-.89 2-2s-.89-2-2-2-2 .89-2 2 .89 2 2 2zM13 22v-7h-2v7h2z"/></svg>""",
"templates": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"/></svg>""",
"add": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>""",
"chevron_down": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M7 10l5 5 5-5z"/></svg>""",
"menu": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M3 6h18v2H3zM3 12h18v2H3zM3 18h18v2H3z"/></svg>""",
"back": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>""",
"share": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.5 2.5 0 000-1.4l7.02-4.11A2.5 2.5 0 0018 7.91a2.5 2.5 0 10-2.5-2.5 2.5 2.5 0 00-.1.71L8.59 10.3a2.5 2.5 0 100 3.4l7.02 4.11a2.5 2.5 0 00-.1.71 2.5 2.5 0 102.5-2.44z"/></svg>""",
"check": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M9 16.17 4.83 12l-1.42 1.41L9 19l12-12-1.41-1.41z"/></svg>""",
"info": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M11 9h2V7h-2v2zm1-7C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6z"/></svg>""",
"filter": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M10 18h4v-2h-4v2zm-7-7v2h18v-2H3zm3-5v2h12V6H6z"/></svg>""",
"user_icon": """<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 12c2.67 0 8 1.34 8 4v2H4v-2c0-2.66 5.33-4 8-4zm0-2a4 4 0 110-8 4 4 0 010 8z"/></svg>"""
}
# -----------------------
# Enhanced Sidebar Generator
# -----------------------
def generate_sidebar(app_title, screens, active_label="Dashboard"):
"""Generate PowerApps-style sidebar with proper sections and icons"""
sidebar_html = f"""
<div class="sidebar">
<div class="sidebar-header">
{SVG_FLUENT['menu']}
<span>{app_title}</span>
</div>
<div class="sidebar-nav">
"""
# Main Navigation
sidebar_html += '<div class="sidebar-section">'
sidebar_html += '<div class="section-label">Navigation</div>'
main_nav = [
("home", "Home"),
("recent", "Recent"),
("pinned", "Pinned"),
("business", "Businesses")
]
for icon_key, label in main_nav:
active_class = "active" if label.lower() == active_label.lower() else ""
sidebar_html += f"""
<div class="sidebar-item {active_class}">
{SVG_FLUENT[icon_key]}
<span>{label}</span>
</div>
"""
sidebar_html += '</div>'
# My Work Section
sidebar_html += '<div class="sidebar-section">'
sidebar_html += '<div class="section-label">My Work</div>'
for i, screen in enumerate(screens):
screen_name = screen.get("screen_name", f"Screen {i+1}")
screen_label = re.sub(r'screen$', '', screen_name, flags=re.IGNORECASE).strip()
active_class = "active" if screen_name == active_label else ""
# Choose appropriate icon based on screen type
if "dashboard" in screen_name.lower():
icon = SVG_FLUENT['home']
elif "search" in screen_name.lower() or "archive" in screen_name.lower():
icon = SVG_FLUENT['search']
elif "workflow" in screen_name.lower() or "submission" in screen_name.lower():
icon = SVG_FLUENT['workflow']
elif "user" in screen_name.lower():
icon = SVG_FLUENT['users']
elif "template" in screen_name.lower():
icon = SVG_FLUENT['templates']
elif "setting" in screen_name.lower():
icon = SVG_FLUENT['settings']
else:
icon = SVG_FLUENT['business']
sidebar_html += f"""
<div class="sidebar-item {active_class}">
{icon}
<span>{screen_label}</span>
</div>
"""
sidebar_html += '</div>'
# Metadata Section
sidebar_html += '<div class="sidebar-section">'
sidebar_html += '<div class="section-label">Metadata</div>'
metadata_items = [
("Document Type", "templates"),
("Division", "business"),
("Process Area", "workflow"),
("Topic", "archive")
]
for label, icon_key in metadata_items:
sidebar_html += f"""
<div class="sidebar-item">
{SVG_FLUENT[icon_key]}
<span>{label}</span>
</div>
"""
sidebar_html += '</div>'
sidebar_html += """
</div>
</div>
"""
return sidebar_html
# -----------------------
# Enhanced Top Bar Generator
# -----------------------
def generate_topbar(screen_name, role="default", user_name="John Joe"):
"""Generate dynamic PowerApps-style top bar with role-based icons"""
initials = "".join([x[0] for x in user_name.split()[:2]]).upper()
# Role-based icon configuration
if role == "dashboard":
left_icon = SVG_FLUENT['menu']
right_icons = f"""
{SVG_FLUENT['share']}
{SVG_FLUENT['info']}
"""
elif role == "list":
left_icon = SVG_FLUENT['menu']
right_icons = f"""
{SVG_FLUENT['add']}
{SVG_FLUENT['filter']}
"""
elif role == "form":
left_icon = SVG_FLUENT['back']
right_icons = f"""
{SVG_FLUENT['check']}
"""
elif role == "settings":
left_icon = SVG_FLUENT['back']
right_icons = f"""
{SVG_FLUENT['info']}
"""
else:
left_icon = SVG_FLUENT['menu']
right_icons = ""
return f"""
<div class="topbar">
<div class="topbar-left">
<div class="topbar-icon">{left_icon}</div>
<div class="topbar-title">{screen_name}</div>
</div>
<div class="topbar-right">
{right_icons}
<div class="user-info">
<span>Welcome, {user_name}</span>
<div class="user-avatar">{initials}</div>
</div>
</div>
</div>
"""
# -----------------------
# Content Generator for Different Screen Types
# -----------------------
def generate_dashboard_content():
"""Generate PowerApps-style dashboard content"""
return """
<div class="tile-grid">
<div class="tile">
<div class="tile-icon">📁</div>
<div class="tile-title">Start New Archival</div>
<div class="tile-description">Upload a document and initiate the archival workflow</div>
</div>
<div class="tile">
<div class="tile-icon">📊</div>
<div class="tile-title">My Submissions</div>
<div class="tile-description">Track the status of your submitted documents</div>
</div>
<div class="tile">
<div class="tile-icon">🔍</div>
<div class="tile-title">Search Archive</div>
<div class="tile-description">Find and view officially archived QMS documents</div>
</div>
</div>
<div class="grid grid-2">
<div class="card">
<div class="card-header">
<div class="card-title">Recent Activity</div>
</div>
<div class="card-body">
<div class="activity-item">
<div class="activity-dot"></div>
<div class="activity-content">
<div class="activity-text">Document "IOS-QMS-WIN-OZ" was successfully archived</div>
<div class="activity-time">2 hours ago</div>
</div>
</div>
<div class="activity-item">
<div class="activity-dot"></div>
<div class="activity-content">
<div class="activity-text">Workflow for "Draft Safety Manual" is pending approval</div>
<div class="activity-time">1 day ago</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">Quick Actions</div>
</div>
<div class="card-body">
<button class="btn btn-primary" style="margin-bottom: 12px; width: 100%;">
{SVG_FLUENT['add']} New Document
</button>
<button class="btn btn-secondary" style="margin-bottom: 12px; width: 100%;">
{SVG_FLUENT['workflow']} View Workflows
</button>
<button class="btn btn-secondary" style="width: 100%;">
{SVG_FLUENT['archive']} Browse Archive
</button>
</div>
</div>
</div>
"""
def generate_list_content(entity_type="Documents"):
"""Generate PowerApps-style list content"""
return f"""
<div class="card">
<div class="card-header">
<div class="card-title">Active {entity_type}</div>
<div class="card-actions">
<button class="btn btn-primary">{SVG_FLUENT['add']} New</button>
<button class="btn btn-secondary">Delete</button>
<button class="btn btn-secondary">Refresh</button>
<button class="btn btn-secondary">Export</button>
</div>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>{entity_type[:-1]} Type</th>
<th>Created By</th>
<th>Created On</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>SOP</td>
<td>Pricing</td>
<td>9/12/2025 8:45 PM</td>
<td><span class="status-badge status-success">Active</span></td>
</tr>
<tr>
<td>MAN</td>
<td>Alain</td>
<td>9/12/2025 8:25 PM</td>
<td><span class="status-badge status-success">Active</span></td>
</tr>
<tr>
<td>RPT</td>
<td>Pricing</td>
<td>9/6/2025 10:35 PM</td>
<td><span class="status-badge status-warning">Pending</span></td>
</tr>
</tbody>
</table>
</div>
</div>
"""
def generate_form_content():
"""Generate PowerApps-style form content"""
return """
<div class="card">
<div class="card-header">
<div class="card-title">New Document Type</div>
</div>
<div class="card-body">
<div class="grid grid-2">
<div class="form-group">
<label class="form-label">Document Type</label>
<input type="text" class="form-control" placeholder="Enter document type">
</div>
<div class="form-group">
<label class="form-label">Created On</label>
<input type="text" class="form-control" value="9/10/2025" readonly>
</div>
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea class="form-control" rows="3" placeholder="Enter description"></textarea>
</div>
</div>
<div class="card-footer">
<button class="btn btn-primary">Save and Close</button>
<button class="btn btn-secondary">Cancel</button>
</div>
</div>
"""
# -----------------------
# Enhanced Content Mapping
# -----------------------
def enhance_generated_content(raw_html, screen_type, screen_name):
"""Enhance the raw HTML generated by Gemini with proper PowerApps styling"""
# If Gemini returns basic HTML, wrap it in proper PowerApps components
if not raw_html or "<div" not in raw_html or "card" not in raw_html:
if "dashboard" in screen_type.lower() or "home" in screen_name.lower():
return generate_dashboard_content()
elif "list" in screen_type.lower() or "table" in screen_name.lower():
return generate_list_content(screen_name)
elif "form" in screen_type.lower() or "new" in screen_name.lower():
return generate_form_content()
else:
# Default card wrapper for unknown content
return f"""
<div class="card">
<div class="card-header">
<div class="card-title">{screen_name}</div>
</div>
<div class="card-body">
{raw_html if raw_html else "<p>Content will be displayed here</p>"}
</div>
</div>
"""
return raw_html
# -----------------------
# Analyze Business PDF (Enhanced)
# -----------------------
def analyze_business_pdf(pdf_file):
text = ""
with pdfplumber.open(pdf_file.name) as pdf:
for page in pdf.pages:
if (t := page.extract_text()):
text += t + "\n"
# Extract user name
user_name = None
possible_name_patterns = [
r"Prepared by[:\-]\s*([A-Z][a-z]+(?:\s[A-Z][a-z]+)+)",
r"Author[:\-]\s*([A-Z][a-z]+(?:\s[A-Z][a-z]+)+)",
r"Created by[:\-]\s*([A-Z][a-z]+(?:\s[A-Z][a-z]+)+)",
r"Owner[:\-]\s*([A-Z][a-z]+(?:\s[A-Z][a-z]+)+)"
]
for pattern in possible_name_patterns:
match = re.search(pattern, text)
if match:
user_name = match.group(1).strip()
break
if not user_name:
user_name = "John Joe"
# Generate app title
try:
app_title = model.generate_content(
"From the following business requirements, infer a concise PowerApp title. Return only the title.\n\n" + text
).text.strip()
except Exception:
app_title = "QMS Document Management"
# Enhanced screen analysis with better structure
try:
prompt = (
"You are a senior PowerApps architect. Analyze the business requirements and return JSON structure like:\n"
"["
" {\"group\": \"My Work\", \"screens\": ["
" {\"screen_name\": \"Dashboard\", \"role\": \"dashboard\", \"screen_type\": \"dashboard\", \"html\": \"<div>...</div>\"},"
" {\"screen_name\": \"Document List\", \"role\": \"list\", \"screen_type\": \"list\", \"html\": \"<div>...</div>\"}"
" ]}"
"]\n"
"- Identify user roles (dashboard, list, form, settings, etc.)\n"
"- Group screens logically (My Work, Metadata, Administration, etc.)\n"
"- Generate appropriate HTML content for each screen\n"
"- Use PowerApps-style components: cards, grids, tables, forms\n"
"- Return valid JSON only.\n\n"
f"Document:\n{text}"
)
response = model.generate_content(prompt)
cleaned = re.sub(r"```(?:json)?|```", "", response.text.strip())
groups = eval(cleaned) if cleaned.strip().startswith("[") else []
except Exception as e:
print("⚠️ Gemini analysis failed:", e)
# Fallback structure
groups = [{
"group": "My Work",
"screens": [
{"screen_name": "Dashboard", "role": "dashboard", "screen_type": "dashboard", "html": ""},
{"screen_name": "My Submissions", "role": "list", "screen_type": "list", "html": ""},
{"screen_name": "Document Archive", "role": "list", "screen_type": "list", "html": ""}
]
}]
return app_title, groups, user_name
# -----------------------
# Generate Mockups (Enhanced)
# -----------------------
def generate_mockups(app_title, groups, user_name):
image_paths = []
for group in groups:
for screen in group.get("screens", []):
label = screen.get("screen_name", "Screen")
screen_type = screen.get("screen_type", "dashboard")
role = screen.get("role", "default")
raw_html = screen.get("html", "")
# Enhance the content with proper PowerApps styling
enhanced_content = enhance_generated_content(raw_html, screen_type, label)
sidebar_html = generate_sidebar(app_title, groups, active_label=label)
topbar_html = generate_topbar(label, role=role, user_name=user_name)
full_html = BASE_TEMPLATE.replace("{user_sidebar}", sidebar_html)\
.replace("{user_content}", enhanced_content)\
.replace("{user_topbar}", topbar_html)
uid = str(uuid.uuid4())[:8]
html_path = f"mockup_{label.replace(' ', '_')}_{uid}.html"
img_path = f"mockup_{label.replace(' ', '_')}_{uid}.png"
with open(html_path, "w", encoding="utf-8") as f:
f.write(full_html)
hti.screenshot(html_file=html_path, save_as=img_path)
image_paths.append(img_path)
return image_paths
# -----------------------
# Retry Wrapper
# -----------------------
def generate_from_pdf(pdf_file):
for attempt in range(3):
try:
app_title, groups, user_name = analyze_business_pdf(pdf_file)
return generate_mockups(app_title, groups, user_name)
except Exception as e:
if "429" in str(e) or "quota" in str(e).lower():
print("⏳ Waiting for Gemini quota reset... retrying in 10 seconds")
time.sleep(10)
else:
raise e
raise Exception("❌ Failed after 3 retries due to Gemini API quota limits.")
# -----------------------
# Gradio UI
# -----------------------
with gr.Blocks() as demo:
gr.Markdown("## 🧩 Intelligent PowerApps Mockup Generator (Enhanced Multi-Screen Mode)")
pdf_input = gr.File(label="📄 Upload Business Requirement PDF", file_types=[".pdf"])
generate_btn = gr.Button("🚀 Generate Mockups")
gallery_output = gr.Gallery(label="Generated Screens", show_label=True, columns=2)
generate_btn.click(fn=generate_from_pdf, inputs=pdf_input, outputs=gallery_output)
demo.launch(server_name="0.0.0.0", server_port=7860)