dina1 commited on
Commit
e264067
·
verified ·
1 Parent(s): a245a66

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -190
app.py CHANGED
@@ -1,3 +1,8 @@
 
 
 
 
 
1
  import google.generativeai as genai
2
  import gradio as gr
3
  from html2image import Html2Image
@@ -19,7 +24,7 @@ model = genai.GenerativeModel("gemini-2.5-pro")
19
  hti = Html2Image(browser_executable="/usr/bin/chromium")
20
 
21
  # -----------------------
22
- # Base Layout Template
23
  # -----------------------
24
  BASE_TEMPLATE = """
25
  <html>
@@ -28,8 +33,11 @@ BASE_TEMPLATE = """
28
  body {
29
  margin: 0;
30
  font-family: 'Segoe UI', sans-serif;
31
- background: #f5f5f5;
 
32
  }
 
 
33
  .sidebar {
34
  width: 240px;
35
  height: 100vh;
@@ -51,7 +59,14 @@ body {
51
  width: 18px;
52
  height: 18px;
53
  margin-right: 8px;
54
- flex-shrink: 0;
 
 
 
 
 
 
 
55
  }
56
  .sidebar-item {
57
  padding: 10px 18px;
@@ -71,34 +86,14 @@ body {
71
  margin-right: 8px;
72
  fill: #c5c5c5;
73
  }
74
- .sidebar-item span {
75
- color: #ccc;
76
- }
77
- .sidebar-item:hover {
78
- background: #3a3a3a;
79
- }
80
- .sidebar-item.active {
81
- background: #0078d4;
82
- color: #fff;
83
- }
84
- .sidebar-item.active svg {
85
- fill: #fff;
86
- }
87
- .sidebar-item-arrow {
88
- width: 14px;
89
- height: 14px;
90
- fill: #aaa;
91
- }
92
- .sidebar-section {
93
- margin-top: 14px;
94
- padding: 8px 18px;
95
- font-size: 12px;
96
- font-weight: bold;
97
- text-transform: uppercase;
98
- color: #aaa;
99
- }
100
  .topbar {
101
- height: 52px;
102
  background: #0078d4;
103
  color: #fff;
104
  display: flex;
@@ -106,41 +101,90 @@ body {
106
  justify-content: space-between;
107
  padding: 0 20px;
108
  font-weight: bold;
109
- box-shadow: 0 1px 4px rgba(0,0,0,0.2);
110
  }
111
- .topbar-left, .topbar-right {
112
  display: flex;
113
  align-items: center;
114
- gap: 10px;
115
  }
116
- .topbar svg {
117
- width: 18px;
118
- height: 18px;
119
- fill: #fff;
120
- cursor: pointer;
121
  }
122
  .topbar-user {
123
  display: flex;
124
  align-items: center;
125
  gap: 8px;
126
- font-weight: normal;
127
  font-size: 14px;
128
  }
129
  .user-avatar {
130
- background: #005a9e;
131
- width: 28px;
132
- height: 28px;
133
  border-radius: 50%;
 
 
134
  display: flex;
135
  align-items: center;
136
  justify-content: center;
137
  font-size: 12px;
138
- font-weight: bold;
139
  }
 
 
140
  .main {
141
  margin-left: 240px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  padding: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  }
 
144
  </style>
145
  </head>
146
  <body>
@@ -152,10 +196,10 @@ body {
152
  """
153
 
154
  # -----------------------
155
- # Fluent SVG Icons
156
  # -----------------------
157
  SVG_FLUENT = {
158
- "hamburger": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z"/></svg>""",
159
  "home": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>""",
160
  "recent": """<svg viewBox="0 0 24 24"><path fill="#fff" 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>""",
161
  "pinned": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M14 2v2l2 2v3l2 2v2H6v-2l2-2V6l2-2V2h4zm-1 13v7h-2v-7h2z"/></svg>""",
@@ -163,14 +207,38 @@ SVG_FLUENT = {
163
  }
164
 
165
  # -----------------------
166
- # Dynamic Top Bar Generator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  # -----------------------
168
  def generate_topbar(screen_name, role="default", user_name="John Joe"):
169
- """Generate dynamic PowerApps-style top bar with role-based icons and user avatar."""
170
  initials = "".join([x[0] for x in user_name.split()[:2]]).upper()
171
-
172
  ICONS = {
173
- "menu": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M3 6h18v2H3zM3 12h18v2H3zM3 18h18v2H3z"/></svg>""",
174
  "back": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>""",
175
  "share": """<svg viewBox="0 0 24 24"><path fill="#fff" 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>""",
176
  "add": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"/></svg>""",
@@ -179,85 +247,21 @@ def generate_topbar(screen_name, role="default", user_name="John Joe"):
179
  "filter": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M10 18h4v-2h-4v2zm-7-7v2h18v-2H3zm3-5v2h12V6H6z"/></svg>""",
180
  "user_icon": """<svg viewBox="0 0 24 24"><path fill="#fff" 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>"""
181
  }
182
-
183
- # Default icons
184
- left_icon = ICONS["menu"]
185
- right_icons = ICONS["user_icon"]
186
-
187
- if role == "dashboard":
188
- right_icons = ICONS["share"] + ICONS["info"] + ICONS["user_icon"]
189
- elif role == "list":
190
- right_icons = ICONS["add"] + ICONS["filter"] + ICONS["user_icon"]
191
- elif role == "form":
192
- left_icon = ICONS["back"]
193
- right_icons = ICONS["check"] + ICONS["user_icon"]
194
- elif role == "settings":
195
- left_icon = ICONS["back"]
196
- right_icons = ICONS["info"] + ICONS["user_icon"]
197
-
198
  return f"""
199
  <div class="topbar">
200
  <div class="topbar-left">{left_icon}<span>{screen_name}</span></div>
201
  <div class="topbar-right">
202
  {right_icons}
203
- <div class="topbar-user">
204
- <span>Welcome, {user_name}</span>
205
- <div class="user-avatar">{initials}</div>
206
- </div>
207
  </div>
208
  </div>
209
  """
210
 
211
- # -----------------------
212
- # Sidebar Generator
213
- # -----------------------
214
- def generate_sidebar(app_title, groups, active_label="Dashboard"):
215
- SCREEN_ICONS = [
216
- """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M3 13h2v-2H3v2zm4 0h14v-2H7v2zm-4 5h2v-2H3v2zm4 0h14v-2H7v2zM3 8h2V6H3v2zm4 0h14V6H7v2z"/></svg>""",
217
- """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M19 3H5a2 2 0 0 0-2 2v14l4-4h12a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z"/></svg>""",
218
- """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M3 3h18v4H3V3zm0 7h18v4H3v-4zm0 7h18v4H3v-4z"/></svg>""",
219
- """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M12 2a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2zm1 15h-2v-2h2zm0-4h-2V7h2z"/></svg>"""
220
- ]
221
-
222
- sidebar_html = "<div class='sidebar'>"
223
- sidebar_html += f"<div class='sidebar-header'>{SVG_FLUENT['hamburger']} {app_title}</div>"
224
-
225
- # Default static options
226
- defaults = [("home", "Home"), ("recent", "Recent"), ("pinned", "Pinned")]
227
- for key, label in defaults:
228
- sidebar_html += f"""
229
- <div class='sidebar-item'>
230
- <div class='sidebar-item-left'>
231
- {SVG_FLUENT[key]}
232
- <span>{label}</span>
233
- </div>
234
- {SVG_FLUENT['chevron_down']}
235
- </div>
236
- """
237
-
238
- # Dynamically render group sections
239
- for group in groups:
240
- group_name = group.get("group", "My Work")
241
- sidebar_html += f"<div class='sidebar-section'>{group_name}</div>"
242
- for i, screen in enumerate(group.get("screens", [])):
243
- screen_name = screen.get("screen_name", f"Screen {i+1}")
244
- screen_label = re.sub(r'screen$', '', screen_name, flags=re.IGNORECASE).strip()
245
- active_class = "active" if screen_name == active_label else ""
246
- icon_svg = SCREEN_ICONS[i % len(SCREEN_ICONS)]
247
- if active_class:
248
- icon_svg = icon_svg.replace("#c5c5c5", "#fff")
249
- sidebar_html += f"""
250
- <div class='sidebar-item {active_class}'>
251
- <div class='sidebar-item-left'>
252
- {icon_svg}
253
- <span>{screen_label}</span>
254
- </div>
255
- </div>
256
- """
257
-
258
- sidebar_html += "</div>"
259
- return sidebar_html
260
-
261
  # -----------------------
262
  # Analyze Business PDF
263
  # -----------------------
@@ -265,89 +269,56 @@ def analyze_business_pdf(pdf_file):
265
  text = ""
266
  with pdfplumber.open(pdf_file.name) as pdf:
267
  for page in pdf.pages:
268
- if (t := page.extract_text()):
269
- text += t + "\n"
270
-
271
- # Try extracting user name from the PDF text
272
- user_name = None
273
- possible_name_patterns = [
274
- r"Prepared by[:\-]\s*([A-Z][a-z]+(?:\s[A-Z][a-z]+)+)",
275
- r"Author[:\-]\s*([A-Z][a-z]+(?:\s[A-Z][a-z]+)+)",
276
- r"Created by[:\-]\s*([A-Z][a-z]+(?:\s[A-Z][a-z]+)+)",
277
- r"Owner[:\-]\s*([A-Z][a-z]+(?:\s[A-Z][a-z]+)+)"
278
- ]
279
-
280
- for pattern in possible_name_patterns:
281
- match = re.search(pattern, text)
282
- if match:
283
- user_name = match.group(1).strip()
284
- break
285
-
286
- if not user_name:
287
- user_name = "John Joe" # Default fallback name
288
-
289
- # Generate concise app title
290
  try:
291
- app_title = model.generate_content(
292
- "From the following business requirements, infer a concise PowerApp title. Return only the title.\n\n" + text
293
- ).text.strip()
294
- except Exception:
295
- app_title = "PowerApp Solution"
296
-
297
- # Ask Gemini for structural, grouped analysis
298
  try:
299
  prompt = (
300
- "You are a senior PowerApps architect. Analyze the business requirements and return JSON structure like:\n"
301
- "["
302
- " {\"group\": \"Metadata\", \"screens\": ["
303
- " {\"screen_name\": \"Metadata Dashboard\", \"role\": \"dashboard\", \"html\": \"<div>...</div>\"}"
304
- " ]}"
305
- "]\n"
306
- "- Identify user roles (dashboard, list, form, settings, etc.)\n"
307
- "- Group screens into modules (Metadata, Templates, Settings...)\n"
308
- "- Return valid JSON only.\n\n"
309
- f"Document:\n{text}"
310
- )
311
-
312
- response = model.generate_content(prompt)
313
- cleaned = re.sub(r"```(?:json)?|```", "", response.text.strip())
314
  groups = eval(cleaned) if cleaned.strip().startswith("[") else []
315
-
316
- except Exception as e:
317
- print("⚠️ Gemini failed:", e)
318
- groups = [{
319
- "group": "My Work",
320
- "screens": [{"screen_name": "Dashboard", "role": "dashboard", "html": "<h2>Error</h2>"}]
321
- }]
322
-
323
  return app_title, groups, user_name
324
 
325
  # -----------------------
326
- # Generate Mockups
327
  # -----------------------
328
  def generate_mockups(app_title, groups, user_name):
329
  image_paths = []
330
  for group in groups:
331
  for screen in group.get("screens", []):
332
- label = screen.get("screen_name", "Screen")
333
- html_content = screen.get("html", "<h2>Empty Screen</h2>")
 
 
 
 
 
 
 
 
 
 
334
  sidebar_html = generate_sidebar(app_title, groups, active_label=label)
335
- topbar_html = generate_topbar(label, role=screen.get("role", "default"), user_name=user_name)
336
-
337
  full_html = BASE_TEMPLATE.replace("{user_sidebar}", sidebar_html)\
338
- .replace("{user_content}", html_content)\
339
- .replace("{user_topbar}", topbar_html)
340
  uid = str(uuid.uuid4())[:8]
341
- html_path = f"mockup_{label.replace(' ', '_')}_{uid}.html"
342
- img_path = f"mockup_{label.replace(' ', '_')}_{uid}.png"
343
- with open(html_path, "w", encoding="utf-8") as f:
344
- f.write(full_html)
345
  hti.screenshot(html_file=html_path, save_as=img_path)
346
  image_paths.append(img_path)
347
  return image_paths
348
 
349
  # -----------------------
350
- # Retry Wrapper
351
  # -----------------------
352
  def generate_from_pdf(pdf_file):
353
  for attempt in range(3):
@@ -355,20 +326,17 @@ def generate_from_pdf(pdf_file):
355
  app_title, groups, user_name = analyze_business_pdf(pdf_file)
356
  return generate_mockups(app_title, groups, user_name)
357
  except Exception as e:
358
- if "429" in str(e) or "quota" in str(e).lower():
359
- print("⏳ Waiting for Gemini quota reset... retrying in 10 seconds")
360
- time.sleep(10)
361
- else:
362
- raise e
363
- raise Exception("❌ Failed after 3 retries due to Gemini API quota limits.")
364
 
365
  # -----------------------
366
  # Gradio UI
367
  # -----------------------
368
  with gr.Blocks() as demo:
369
- gr.Markdown("## 🧩 Intelligent PowerApps Mockup Generator (Multi-Screen Mode)")
370
  pdf_input = gr.File(label="📄 Upload Business Requirement PDF", file_types=[".pdf"])
371
- generate_btn = gr.Button("🚀 Generate Mockups")
372
  gallery_output = gr.Gallery(label="Generated Screens", show_label=True, columns=2)
373
  generate_btn.click(fn=generate_from_pdf, inputs=pdf_input, outputs=gallery_output)
374
 
 
1
+ # =========================================================
2
+ # Intelligent PowerApps Mockup Generator (Full Smart Edition)
3
+ # From Business Requirement PDF → Multi-Screen PowerApps-Style Mockup Images
4
+ # =========================================================
5
+
6
  import google.generativeai as genai
7
  import gradio as gr
8
  from html2image import Html2Image
 
24
  hti = Html2Image(browser_executable="/usr/bin/chromium")
25
 
26
  # -----------------------
27
+ # Base PowerApps Layout Template
28
  # -----------------------
29
  BASE_TEMPLATE = """
30
  <html>
 
33
  body {
34
  margin: 0;
35
  font-family: 'Segoe UI', sans-serif;
36
+ background: #f3f3f3;
37
+ color: #323130;
38
  }
39
+
40
+ /* --- Sidebar --- */
41
  .sidebar {
42
  width: 240px;
43
  height: 100vh;
 
59
  width: 18px;
60
  height: 18px;
61
  margin-right: 8px;
62
+ }
63
+ .sidebar-section {
64
+ margin-top: 14px;
65
+ padding: 8px 18px;
66
+ font-size: 12px;
67
+ font-weight: bold;
68
+ text-transform: uppercase;
69
+ color: #aaa;
70
  }
71
  .sidebar-item {
72
  padding: 10px 18px;
 
86
  margin-right: 8px;
87
  fill: #c5c5c5;
88
  }
89
+ .sidebar-item span { color: #ccc; }
90
+ .sidebar-item:hover { background: #3a3a3a; }
91
+ .sidebar-item.active { background: #0078d4; color: #fff; }
92
+ .sidebar-item.active svg { fill: #fff; }
93
+
94
+ /* --- Top Bar --- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  .topbar {
96
+ height: 50px;
97
  background: #0078d4;
98
  color: #fff;
99
  display: flex;
 
101
  justify-content: space-between;
102
  padding: 0 20px;
103
  font-weight: bold;
104
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
105
  }
106
+ .topbar-left {
107
  display: flex;
108
  align-items: center;
109
+ gap: 8px;
110
  }
111
+ .topbar-right {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 12px;
 
115
  }
116
  .topbar-user {
117
  display: flex;
118
  align-items: center;
119
  gap: 8px;
 
120
  font-size: 14px;
121
  }
122
  .user-avatar {
123
+ width: 26px;
124
+ height: 26px;
 
125
  border-radius: 50%;
126
+ background: #004578;
127
+ color: #fff;
128
  display: flex;
129
  align-items: center;
130
  justify-content: center;
131
  font-size: 12px;
 
132
  }
133
+
134
+ /* --- Main Canvas --- */
135
  .main {
136
  margin-left: 240px;
137
+ padding: 40px 60px;
138
+ background: #f9f9f9;
139
+ min-height: calc(100vh - 50px);
140
+ display: flex;
141
+ flex-direction: column;
142
+ }
143
+
144
+ /* --- PowerApps-Like Components --- */
145
+ .app-card {
146
+ background: #fff;
147
+ border-radius: 10px;
148
+ padding: 20px 25px;
149
+ box-shadow: 0 2px 6px rgba(0,0,0,0.08);
150
+ margin-bottom: 20px;
151
+ }
152
+ .app-card h2, .app-card h3 {
153
+ margin-top: 0;
154
+ color: #0078d4;
155
+ }
156
+ .app-section-title {
157
+ font-weight: 600;
158
+ font-size: 18px;
159
+ margin-bottom: 10px;
160
+ }
161
+ .app-grid {
162
+ display: grid;
163
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
164
+ gap: 20px;
165
+ }
166
+ .app-stat {
167
+ background: #fff;
168
+ border-radius: 10px;
169
  padding: 20px;
170
+ text-align: center;
171
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
172
+ }
173
+ .app-stat-value {
174
+ color: #0078d4;
175
+ font-size: 28px;
176
+ font-weight: bold;
177
+ }
178
+ button {
179
+ background: #0078d4;
180
+ color: #fff;
181
+ border: none;
182
+ padding: 8px 16px;
183
+ border-radius: 5px;
184
+ cursor: pointer;
185
+ transition: background 0.2s;
186
  }
187
+ button:hover { background: #005a9e; }
188
  </style>
189
  </head>
190
  <body>
 
196
  """
197
 
198
  # -----------------------
199
+ # Fluent Icons
200
  # -----------------------
201
  SVG_FLUENT = {
202
+ "hamburger": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M3 6h18v2H3zM3 12h18v2H3zM3 18h18v2H3z"/></svg>""",
203
  "home": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>""",
204
  "recent": """<svg viewBox="0 0 24 24"><path fill="#fff" 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>""",
205
  "pinned": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M14 2v2l2 2v3l2 2v2H6v-2l2-2V6l2-2V2h4zm-1 13v7h-2v-7h2z"/></svg>""",
 
207
  }
208
 
209
  # -----------------------
210
+ # Sidebar Generator
211
+ # -----------------------
212
+ def generate_sidebar(app_title, groups, active_label="Dashboard"):
213
+ sidebar_html = "<div class='sidebar'>"
214
+ sidebar_html += f"<div class='sidebar-header'>{SVG_FLUENT['hamburger']} {app_title}</div>"
215
+ defaults = [("home", "Home"), ("recent", "Recent"), ("pinned", "Pinned")]
216
+ for key, label in defaults:
217
+ sidebar_html += f"""
218
+ <div class='sidebar-item'>
219
+ <div class='sidebar-item-left'>
220
+ {SVG_FLUENT[key]}<span>{label}</span>
221
+ </div>
222
+ {SVG_FLUENT['chevron_down']}
223
+ </div>
224
+ """
225
+ for group in groups:
226
+ group_name = group.get("group", "Section").replace("Screen", "").strip()
227
+ sidebar_html += f"<div class='sidebar-section'>{group_name.upper()}</div>"
228
+ for screen in group.get("screens", []):
229
+ screen_name = screen.get("screen_name", "Screen").replace("Screen", "").strip()
230
+ active_class = "active" if screen_name == active_label else ""
231
+ sidebar_html += f"<div class='sidebar-item {active_class}'><div class='sidebar-item-left'><span>{screen_name}</span></div></div>"
232
+ sidebar_html += "</div>"
233
+ return sidebar_html
234
+
235
+ # -----------------------
236
+ # Top Bar Generator
237
  # -----------------------
238
  def generate_topbar(screen_name, role="default", user_name="John Joe"):
 
239
  initials = "".join([x[0] for x in user_name.split()[:2]]).upper()
 
240
  ICONS = {
241
+ "menu": SVG_FLUENT["hamburger"],
242
  "back": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>""",
243
  "share": """<svg viewBox="0 0 24 24"><path fill="#fff" 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>""",
244
  "add": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"/></svg>""",
 
247
  "filter": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M10 18h4v-2h-4v2zm-7-7v2h18v-2H3zm3-5v2h12V6H6z"/></svg>""",
248
  "user_icon": """<svg viewBox="0 0 24 24"><path fill="#fff" 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>"""
249
  }
250
+ left_icon, right_icons = ICONS["menu"], ICONS["user_icon"]
251
+ if role == "dashboard": right_icons = ICONS["share"] + ICONS["info"] + ICONS["user_icon"]
252
+ elif role == "list": right_icons = ICONS["add"] + ICONS["filter"] + ICONS["user_icon"]
253
+ elif role == "form": left_icon, right_icons = ICONS["back"], ICONS["check"] + ICONS["user_icon"]
254
+ elif role == "settings": left_icon, right_icons = ICONS["back"], ICONS["info"] + ICONS["user_icon"]
 
 
 
 
 
 
 
 
 
 
 
255
  return f"""
256
  <div class="topbar">
257
  <div class="topbar-left">{left_icon}<span>{screen_name}</span></div>
258
  <div class="topbar-right">
259
  {right_icons}
260
+ <div class="topbar-user"><span>Welcome, {user_name}</span><div class="user-avatar">{initials}</div></div>
 
 
 
261
  </div>
262
  </div>
263
  """
264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  # -----------------------
266
  # Analyze Business PDF
267
  # -----------------------
 
269
  text = ""
270
  with pdfplumber.open(pdf_file.name) as pdf:
271
  for page in pdf.pages:
272
+ if (t := page.extract_text()): text += t + "\n"
273
+ user_name = "John Joe"
274
+ for pattern in [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]+)+)"]:
275
+ if m := re.search(pattern, text): user_name = m.group(1).strip(); break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  try:
277
+ app_title = model.generate_content("From the following business requirements, infer a concise PowerApp title.\n\n" + text).text.strip()
278
+ except Exception: app_title = "PowerApp Solution"
 
 
 
 
 
279
  try:
280
  prompt = (
281
+ "You are a senior PowerApps architect. Analyze the document and output JSON like:\n"
282
+ "[{\"group\":\"Metadata\",\"screens\":[{\"screen_name\":\"Metadata Dashboard\",\"role\":\"dashboard\",\"html\":\"<div>...</div>\"}]}]"
283
+ "\nDetect functional modules, classify screen roles (dashboard, list, form, settings, detail), and generate compact valid JSON.\n\n" + text)
284
+ cleaned = re.sub(r"```(?:json)?|```", "", model.generate_content(prompt).text.strip())
 
 
 
 
 
 
 
 
 
 
285
  groups = eval(cleaned) if cleaned.strip().startswith("[") else []
286
+ except Exception: groups = [{"group": "My Work", "screens": [{"screen_name": "Dashboard", "role": "dashboard", "html": "<h2>Error</h2>"}]}]
 
 
 
 
 
 
 
287
  return app_title, groups, user_name
288
 
289
  # -----------------------
290
+ # Generate HTML + Image
291
  # -----------------------
292
  def generate_mockups(app_title, groups, user_name):
293
  image_paths = []
294
  for group in groups:
295
  for screen in group.get("screens", []):
296
+ label, role = screen.get("screen_name", "Screen"), screen.get("role", "default")
297
+ raw_html = screen.get("html", "<h2>Empty Screen</h2>")
298
+ html_content = f"<div class='app-card'>{raw_html}</div>"
299
+ if role == "dashboard":
300
+ html_content = f"""
301
+ <div class='app-grid'>
302
+ <div class='app-stat'><div class='app-section-title'>Documents in Staging</div><div class='app-stat-value'>12</div></div>
303
+ <div class='app-stat'><div class='app-section-title'>Archived This Month</div><div class='app-stat-value'>8</div></div>
304
+ <div class='app-stat'><div class='app-section-title'>Total Archived</div><div class='app-stat-value'>1,245</div></div>
305
+ </div>
306
+ <div class='app-card'>{raw_html}</div>
307
+ """
308
  sidebar_html = generate_sidebar(app_title, groups, active_label=label)
309
+ topbar_html = generate_topbar(label, role=role, user_name=user_name)
 
310
  full_html = BASE_TEMPLATE.replace("{user_sidebar}", sidebar_html)\
311
+ .replace("{user_topbar}", topbar_html)\
312
+ .replace("{user_content}", html_content)
313
  uid = str(uuid.uuid4())[:8]
314
+ html_path, img_path = f"mockup_{label.replace(' ', '_')}_{uid}.html", f"mockup_{label.replace(' ', '_')}_{uid}.png"
315
+ with open(html_path, "w", encoding="utf-8") as f: f.write(full_html)
 
 
316
  hti.screenshot(html_file=html_path, save_as=img_path)
317
  image_paths.append(img_path)
318
  return image_paths
319
 
320
  # -----------------------
321
+ # Full Pipeline
322
  # -----------------------
323
  def generate_from_pdf(pdf_file):
324
  for attempt in range(3):
 
326
  app_title, groups, user_name = analyze_business_pdf(pdf_file)
327
  return generate_mockups(app_title, groups, user_name)
328
  except Exception as e:
329
+ if "429" in str(e) or "quota" in str(e).lower(): time.sleep(10)
330
+ else: raise e
331
+ raise Exception("❌ Failed after 3 retries.")
 
 
 
332
 
333
  # -----------------------
334
  # Gradio UI
335
  # -----------------------
336
  with gr.Blocks() as demo:
337
+ gr.Markdown("## 🧩 Intelligent PowerApps Mockup Generator (Full Smart Edition)")
338
  pdf_input = gr.File(label="📄 Upload Business Requirement PDF", file_types=[".pdf"])
339
+ generate_btn = gr.Button("🚀 Generate PowerApps Mockups")
340
  gallery_output = gr.Gallery(label="Generated Screens", show_label=True, columns=2)
341
  generate_btn.click(fn=generate_from_pdf, inputs=pdf_input, outputs=gallery_output)
342