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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -154
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # =========================================================
2
- # Intelligent PowerApps Mockup Generator (Full Smart Edition)
3
  # From Business Requirement PDF → Multi-Screen PowerApps-Style Mockup Images
4
  # =========================================================
5
 
@@ -24,7 +24,7 @@ model = genai.GenerativeModel("gemini-2.5-pro")
24
  hti = Html2Image(browser_executable="/usr/bin/chromium")
25
 
26
  # -----------------------
27
- # Base PowerApps Layout Template
28
  # -----------------------
29
  BASE_TEMPLATE = """
30
  <html>
@@ -33,11 +33,8 @@ BASE_TEMPLATE = """
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,14 +56,7 @@ body {
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,14 +76,34 @@ body {
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,90 +111,41 @@ body {
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,10 +157,10 @@ button:hover { background: #005a9e; }
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,38 +168,14 @@ SVG_FLUENT = {
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,21 +184,80 @@ def generate_topbar(screen_name, role="default", user_name="John Joe"):
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,56 +265,89 @@ def analyze_business_pdf(pdf_file):
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,17 +355,20 @@ def generate_from_pdf(pdf_file):
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
 
 
1
  # =========================================================
2
+ # Intelligent PowerApps Mockup Generator
3
  # From Business Requirement PDF → Multi-Screen PowerApps-Style Mockup Images
4
  # =========================================================
5
 
 
24
  hti = Html2Image(browser_executable="/usr/bin/chromium")
25
 
26
  # -----------------------
27
+ # Base Layout Template
28
  # -----------------------
29
  BASE_TEMPLATE = """
30
  <html>
 
33
  body {
34
  margin: 0;
35
  font-family: 'Segoe UI', sans-serif;
36
+ background: #f5f5f5;
 
37
  }
 
 
38
  .sidebar {
39
  width: 240px;
40
  height: 100vh;
 
56
  width: 18px;
57
  height: 18px;
58
  margin-right: 8px;
59
+ flex-shrink: 0;
 
 
 
 
 
 
 
60
  }
61
  .sidebar-item {
62
  padding: 10px 18px;
 
76
  margin-right: 8px;
77
  fill: #c5c5c5;
78
  }
79
+ .sidebar-item span {
80
+ color: #ccc;
81
+ }
82
+ .sidebar-item:hover {
83
+ background: #3a3a3a;
84
+ }
85
+ .sidebar-item.active {
86
+ background: #0078d4;
87
+ color: #fff;
88
+ }
89
+ .sidebar-item.active svg {
90
+ fill: #fff;
91
+ }
92
+ .sidebar-item-arrow {
93
+ width: 14px;
94
+ height: 14px;
95
+ fill: #aaa;
96
+ }
97
+ .sidebar-section {
98
+ margin-top: 14px;
99
+ padding: 8px 18px;
100
+ font-size: 12px;
101
+ font-weight: bold;
102
+ text-transform: uppercase;
103
+ color: #aaa;
104
+ }
105
  .topbar {
106
+ height: 52px;
107
  background: #0078d4;
108
  color: #fff;
109
  display: flex;
 
111
  justify-content: space-between;
112
  padding: 0 20px;
113
  font-weight: bold;
114
+ box-shadow: 0 1px 4px rgba(0,0,0,0.2);
115
  }
116
+ .topbar-left, .topbar-right {
117
  display: flex;
118
  align-items: center;
119
+ gap: 10px;
120
  }
121
+ .topbar svg {
122
+ width: 18px;
123
+ height: 18px;
124
+ fill: #fff;
125
+ cursor: pointer;
126
  }
127
  .topbar-user {
128
  display: flex;
129
  align-items: center;
130
  gap: 8px;
131
+ font-weight: normal;
132
  font-size: 14px;
133
  }
134
  .user-avatar {
135
+ background: #005a9e;
136
+ width: 28px;
137
+ height: 28px;
138
  border-radius: 50%;
 
 
139
  display: flex;
140
  align-items: center;
141
  justify-content: center;
142
  font-size: 12px;
143
+ font-weight: bold;
144
  }
 
 
145
  .main {
146
  margin-left: 240px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  padding: 20px;
 
 
 
 
 
 
 
148
  }
 
 
 
 
 
 
 
 
 
 
149
  </style>
150
  </head>
151
  <body>
 
157
  """
158
 
159
  # -----------------------
160
+ # Fluent SVG Icons
161
  # -----------------------
162
  SVG_FLUENT = {
163
+ "hamburger": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z"/></svg>""",
164
  "home": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>""",
165
  "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>""",
166
  "pinned": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M14 2v2l2 2v3l2 2v2H6v-2l2-2V6l2-2V2h4zm-1 13v7h-2v-7h2z"/></svg>""",
 
168
  }
169
 
170
  # -----------------------
171
+ # Dynamic Top Bar Generator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  # -----------------------
173
  def generate_topbar(screen_name, role="default", user_name="John Joe"):
174
+ """Generate dynamic PowerApps-style top bar with role-based icons and user avatar."""
175
  initials = "".join([x[0] for x in user_name.split()[:2]]).upper()
176
+
177
  ICONS = {
178
+ "menu": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M3 6h18v2H3zM3 12h18v2H3zM3 18h18v2H3z"/></svg>""",
179
  "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>""",
180
  "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>""",
181
  "add": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"/></svg>""",
 
184
  "filter": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M10 18h4v-2h-4v2zm-7-7v2h18v-2H3zm3-5v2h12V6H6z"/></svg>""",
185
  "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>"""
186
  }
187
+
188
+ # Default icons
189
+ left_icon = ICONS["menu"]
190
+ right_icons = ICONS["user_icon"]
191
+
192
+ if role == "dashboard":
193
+ right_icons = ICONS["share"] + ICONS["info"] + ICONS["user_icon"]
194
+ elif role == "list":
195
+ right_icons = ICONS["add"] + ICONS["filter"] + ICONS["user_icon"]
196
+ elif role == "form":
197
+ left_icon = ICONS["back"]
198
+ right_icons = ICONS["check"] + ICONS["user_icon"]
199
+ elif role == "settings":
200
+ left_icon = ICONS["back"]
201
+ right_icons = ICONS["info"] + ICONS["user_icon"]
202
+
203
  return f"""
204
  <div class="topbar">
205
  <div class="topbar-left">{left_icon}<span>{screen_name}</span></div>
206
  <div class="topbar-right">
207
  {right_icons}
208
+ <div class="topbar-user">
209
+ <span>Welcome, {user_name}</span>
210
+ <div class="user-avatar">{initials}</div>
211
+ </div>
212
  </div>
213
  </div>
214
  """
215
 
216
+ # -----------------------
217
+ # Sidebar Generator
218
+ # -----------------------
219
+ def generate_sidebar(app_title, screens, active_label="Dashboard"):
220
+ SCREEN_ICONS = [
221
+ """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M3 13h2v-2H3v2zm4 0h14v-2H7v2zm-4 5h2v-2H3v2zm4 0h14v-2H7v2zM3 8h2V6H3v2zm4 0h14V6H7v2z"/></svg>""",
222
+ """<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>""",
223
+ """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M3 3h18v4H3V3zm0 7h18v4H3v-4zm0 7h18v4H3v-4z"/></svg>""",
224
+ """<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>"""
225
+ ]
226
+
227
+ sidebar_html = "<div class='sidebar'>"
228
+ sidebar_html += f"<div class='sidebar-header'>{SVG_FLUENT['hamburger']} {app_title}</div>"
229
+
230
+ defaults = [("home", "Home"), ("recent", "Recent"), ("pinned", "Pinned")]
231
+ for key, label in defaults:
232
+ sidebar_html += f"""
233
+ <div class='sidebar-item'>
234
+ <div class='sidebar-item-left'>
235
+ {SVG_FLUENT[key]}
236
+ <span>{label}</span>
237
+ </div>
238
+ {SVG_FLUENT['chevron_down']}
239
+ </div>
240
+ """
241
+
242
+ sidebar_html += "<div class='sidebar-section'>My Work</div>"
243
+ for i, screen in enumerate(screens):
244
+ screen_name = screen.get("screen_name", f"Screen {i+1}")
245
+ screen_label = re.sub(r'screen$', '', screen_name, flags=re.IGNORECASE).strip()
246
+ active_class = "active" if screen_name == active_label else ""
247
+ icon_svg = SCREEN_ICONS[i % len(SCREEN_ICONS)]
248
+ if active_class:
249
+ icon_svg = icon_svg.replace("#c5c5c5", "#fff")
250
+ sidebar_html += f"""
251
+ <div class='sidebar-item {active_class}'>
252
+ <div class='sidebar-item-left'>
253
+ {icon_svg}
254
+ <span>{screen_label}</span>
255
+ </div>
256
+ </div>
257
+ """
258
+ sidebar_html += "</div>"
259
+ return sidebar_html
260
+
261
  # -----------------------
262
  # Analyze Business PDF
263
  # -----------------------
 
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
  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