Download app.py from dina1/image1: direct link, hf CLI and curl.
- Browser
- Download file 30.1 kB
-
https://huggingface.co/spaces/dina1/image1/resolve/main/app.py
- Command line
-
hf download hf://spaces/dina1/image1/app.py
-
curl -L -o app.py https://huggingface.co/spaces/dina1/image1/resolve/main/app.py
30.1 kB
| # ========================================================= | |
| # 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) |