dina1 commited on
Commit
ca30772
·
verified ·
1 Parent(s): 19d79ef

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -36
app.py CHANGED
@@ -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>
@@ -57,7 +57,6 @@ body {
57
  height: 18px;
58
  margin-right: 8px;
59
  flex-shrink: 0;
60
- vertical-align: middle;
61
  }
62
  .sidebar-item {
63
  padding: 10px 18px;
@@ -104,20 +103,45 @@ body {
104
  color: #aaa;
105
  }
106
  .topbar {
107
- height: 50px;
108
  background: #0078d4;
109
  color: #fff;
110
  display: flex;
111
  align-items: center;
112
- padding: 0 20px;
113
  justify-content: space-between;
 
114
  font-weight: bold;
 
115
  }
116
  .topbar-left, .topbar-right {
117
  display: flex;
118
  align-items: center;
119
  gap: 10px;
120
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  .main {
122
  margin-left: 240px;
123
  padding: 20px;
@@ -133,7 +157,7 @@ body {
133
  """
134
 
135
  # -----------------------
136
- # Default Fluent Icons
137
  # -----------------------
138
  SVG_FLUENT = {
139
  "hamburger": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z"/></svg>""",
@@ -146,33 +170,33 @@ SVG_FLUENT = {
146
  # -----------------------
147
  # Dynamic Top Bar Generator
148
  # -----------------------
149
- def generate_topbar(screen_name):
150
- """Generate a dynamic top bar layout depending on screen name."""
151
- screen_name_lower = screen_name.lower()
152
 
153
  ICONS = {
154
- "back": """<svg viewBox="0 0 24 24" width="18" height="18" fill="#fff"><path d="M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>""",
155
- "menu": """<svg viewBox="0 0 24 24" width="18" height="18" fill="#fff"><path d="M3 6h18v2H3zM3 12h18v2H3zM3 18h18v2H3z"/></svg>""",
156
- "share": """<svg viewBox="0 0 24 24" width="18" height="18" fill="#fff"><path 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>""",
157
- "add": """<svg viewBox="0 0 24 24" width="18" height="18" fill="#fff"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"/></svg>""",
158
- "check": """<svg viewBox="0 0 24 24" width="18" height="18" fill="#fff"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19l12-12-1.41-1.41z"/></svg>""",
159
- "info": """<svg viewBox="0 0 24 24" width="18" height="18" fill="#fff"><path 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>""",
160
- "filter": """<svg viewBox="0 0 24 24" width="18" height="18" fill="#fff"><path d="M10 18h4v-2h-4v2zm-7-7v2h18v-2H3zm3-5v2h12V6H6z"/></svg>"""
161
  }
162
 
163
- left_icon = ICONS["menu"]
164
- right_icons = ICONS["share"] + ICONS["info"]
165
 
166
- if "dashboard" in screen_name_lower:
167
  left_icon = ICONS["menu"]
168
  right_icons = ICONS["share"] + ICONS["info"]
169
- elif "repository" in screen_name_lower or "library" in screen_name_lower:
170
  left_icon = ICONS["menu"]
171
  right_icons = ICONS["filter"] + ICONS["add"]
172
- elif "task" in screen_name_lower:
173
  left_icon = ICONS["menu"]
174
  right_icons = ICONS["filter"]
175
- elif "form" in screen_name_lower or "edit" in screen_name_lower or "add" in screen_name_lower:
176
  left_icon = ICONS["back"]
177
  right_icons = ICONS["check"]
178
  else:
@@ -181,8 +205,17 @@ def generate_topbar(screen_name):
181
 
182
  return f"""
183
  <div class="topbar">
184
- <div class="topbar-left">{left_icon}<span>{screen_name}</span></div>
185
- <div class="topbar-right">{right_icons}</div>
 
 
 
 
 
 
 
 
 
186
  </div>
187
  """
188
 
@@ -194,9 +227,7 @@ def generate_sidebar(app_title, screens, active_label="Dashboard"):
194
  """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M3 13h2v-2H3v2zm4 0h14v-2H7v2zm-4 5h2v-2H3v2zm4 0h14v-2H7v2zM3 8h2V6H3v2zm4 0h14V6H7v2z"/></svg>""",
195
  """<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>""",
196
  """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M3 3h18v4H3V3zm0 7h18v4H3v-4zm0 7h18v4H3v-4z"/></svg>""",
197
- """<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>""",
198
- """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M12 4a8 8 0 0 0 0 16 8 8 0 1 0 0-16zm1 11h-2v-2h2zm0-4h-2V7h2z"/></svg>""",
199
- """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M12 2L2 7l10 5 10-5-10-5z"/></svg>"""
200
  ]
201
 
202
  sidebar_html = "<div class='sidebar'>"
@@ -217,7 +248,6 @@ def generate_sidebar(app_title, screens, active_label="Dashboard"):
217
  sidebar_html += "<div class='sidebar-section'>My Work</div>"
218
  for i, screen in enumerate(screens):
219
  screen_name = screen.get("screen_name", f"Screen {i+1}")
220
- # remove 'Screen' suffix if present
221
  screen_label = re.sub(r'screen$', '', screen_name, flags=re.IGNORECASE).strip()
222
  active_class = "active" if screen_name == active_label else ""
223
  icon_svg = SCREEN_ICONS[i % len(SCREEN_ICONS)]
@@ -235,7 +265,7 @@ def generate_sidebar(app_title, screens, active_label="Dashboard"):
235
  return sidebar_html
236
 
237
  # -----------------------
238
- # Analyze PDF
239
  # -----------------------
240
  def analyze_business_pdf(pdf_file):
241
  text = ""
@@ -253,15 +283,14 @@ def analyze_business_pdf(pdf_file):
253
 
254
  try:
255
  response = model.generate_content(
256
- "You are a PowerApps UI designer. Infer multiple PowerApps screens. "
257
- "Return JSON like: [{\"screen_name\": \"Dashboard\", \"html\": \"<div>...</div>\"}]\n\n" + text
258
  )
259
  cleaned = re.sub(r"```(?:json)?|```", "", response.text.strip())
260
  screens = eval(cleaned) if cleaned.strip().startswith("[") else []
261
  except Exception as e:
262
  print("⚠️ Gemini failed:", e)
263
- screens = [{"screen_name": "Dashboard", "html": "<h2>Error</h2>"}]
264
-
265
  return app_title, screens
266
 
267
  # -----------------------
@@ -274,11 +303,9 @@ def generate_mockups(app_title, screens):
274
  html_content = screen.get("html", "<h2>Empty Screen</h2>")
275
  sidebar_html = generate_sidebar(app_title, screens, active_label=label)
276
  topbar_html = generate_topbar(label)
277
-
278
  full_html = BASE_TEMPLATE.replace("{user_sidebar}", sidebar_html)\
279
  .replace("{user_content}", html_content)\
280
  .replace("{user_topbar}", topbar_html)
281
-
282
  uid = str(uuid.uuid4())[:8]
283
  html_path = f"mockup_{label.replace(' ', '_')}_{uid}.html"
284
  img_path = f"mockup_{label.replace(' ', '_')}_{uid}.png"
@@ -289,7 +316,7 @@ def generate_mockups(app_title, screens):
289
  return image_paths
290
 
291
  # -----------------------
292
- # Full Pipeline
293
  # -----------------------
294
  def generate_from_pdf(pdf_file):
295
  for attempt in range(3):
@@ -298,10 +325,11 @@ def generate_from_pdf(pdf_file):
298
  return generate_mockups(app_title, screens)
299
  except Exception as e:
300
  if "429" in str(e) or "quota" in str(e).lower():
 
301
  time.sleep(10)
302
  else:
303
  raise e
304
- raise Exception("❌ Failed after 3 retries due to Gemini API quota limits.")
305
 
306
  # -----------------------
307
  # Gradio UI
 
24
  hti = Html2Image(browser_executable="/usr/bin/chromium")
25
 
26
  # -----------------------
27
+ # Base Layout Template
28
  # -----------------------
29
  BASE_TEMPLATE = """
30
  <html>
 
57
  height: 18px;
58
  margin-right: 8px;
59
  flex-shrink: 0;
 
60
  }
61
  .sidebar-item {
62
  padding: 10px 18px;
 
103
  color: #aaa;
104
  }
105
  .topbar {
106
+ height: 52px;
107
  background: #0078d4;
108
  color: #fff;
109
  display: flex;
110
  align-items: center;
 
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;
 
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>""",
 
170
  # -----------------------
171
  # Dynamic Top Bar Generator
172
  # -----------------------
173
+ def generate_topbar(screen_name, user_name="Mauricio Moutinho"):
174
+ """Generate top bar based on screen type."""
175
+ initials = "".join([x[0] for x in user_name.split()[:2]]).upper()
176
 
177
  ICONS = {
178
+ "back": """<svg viewBox="0 0 24 24"><path d="M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>""",
179
+ "menu": """<svg viewBox="0 0 24 24"><path d="M3 6h18v2H3zM3 12h18v2H3zM3 18h18v2H3z"/></svg>""",
180
+ "share": """<svg viewBox="0 0 24 24"><path 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 d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"/></svg>""",
182
+ "check": """<svg viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19l12-12-1.41-1.41z"/></svg>""",
183
+ "info": """<svg viewBox="0 0 24 24"><path 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>""",
184
+ "filter": """<svg viewBox="0 0 24 24"><path d="M10 18h4v-2h-4v2zm-7-7v2h18v-2H3zm3-5v2h12V6H6z"/></svg>"""
185
  }
186
 
187
+ name_lower = screen_name.lower()
188
+ left_icon, right_icons = ICONS["menu"], ICONS["share"] + ICONS["info"]
189
 
190
+ if "dashboard" in name_lower:
191
  left_icon = ICONS["menu"]
192
  right_icons = ICONS["share"] + ICONS["info"]
193
+ elif "repository" in name_lower or "library" in name_lower:
194
  left_icon = ICONS["menu"]
195
  right_icons = ICONS["filter"] + ICONS["add"]
196
+ elif "task" in name_lower:
197
  left_icon = ICONS["menu"]
198
  right_icons = ICONS["filter"]
199
+ elif "form" in name_lower or "edit" in name_lower or "add" in name_lower:
200
  left_icon = ICONS["back"]
201
  right_icons = ICONS["check"]
202
  else:
 
205
 
206
  return f"""
207
  <div class="topbar">
208
+ <div class="topbar-left">
209
+ {left_icon}
210
+ <span>{screen_name}</span>
211
+ </div>
212
+ <div class="topbar-right">
213
+ {right_icons}
214
+ <div class="topbar-user">
215
+ <span>Welcome, {user_name}</span>
216
+ <div class="user-avatar">{initials}</div>
217
+ </div>
218
+ </div>
219
  </div>
220
  """
221
 
 
227
  """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M3 13h2v-2H3v2zm4 0h14v-2H7v2zm-4 5h2v-2H3v2zm4 0h14v-2H7v2zM3 8h2V6H3v2zm4 0h14V6H7v2z"/></svg>""",
228
  """<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>""",
229
  """<svg viewBox="0 0 24 24"><path fill="#c5c5c5" d="M3 3h18v4H3V3zm0 7h18v4H3v-4zm0 7h18v4H3v-4z"/></svg>""",
230
+ """<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>"""
 
 
231
  ]
232
 
233
  sidebar_html = "<div class='sidebar'>"
 
248
  sidebar_html += "<div class='sidebar-section'>My Work</div>"
249
  for i, screen in enumerate(screens):
250
  screen_name = screen.get("screen_name", f"Screen {i+1}")
 
251
  screen_label = re.sub(r'screen$', '', screen_name, flags=re.IGNORECASE).strip()
252
  active_class = "active" if screen_name == active_label else ""
253
  icon_svg = SCREEN_ICONS[i % len(SCREEN_ICONS)]
 
265
  return sidebar_html
266
 
267
  # -----------------------
268
+ # Analyze Business PDF
269
  # -----------------------
270
  def analyze_business_pdf(pdf_file):
271
  text = ""
 
283
 
284
  try:
285
  response = model.generate_content(
286
+ "You are a PowerApps UI designer. Infer multiple PowerApps screens with HTML layouts.\n"
287
+ "Return JSON like: [{\"screen_name\": \"DashboardScreen\", \"html\": \"<div>...</div>\"}]\n\n" + text
288
  )
289
  cleaned = re.sub(r"```(?:json)?|```", "", response.text.strip())
290
  screens = eval(cleaned) if cleaned.strip().startswith("[") else []
291
  except Exception as e:
292
  print("⚠️ Gemini failed:", e)
293
+ screens = [{"screen_name": "DashboardScreen", "html": "<h2>Error</h2>"}]
 
294
  return app_title, screens
295
 
296
  # -----------------------
 
303
  html_content = screen.get("html", "<h2>Empty Screen</h2>")
304
  sidebar_html = generate_sidebar(app_title, screens, active_label=label)
305
  topbar_html = generate_topbar(label)
 
306
  full_html = BASE_TEMPLATE.replace("{user_sidebar}", sidebar_html)\
307
  .replace("{user_content}", html_content)\
308
  .replace("{user_topbar}", topbar_html)
 
309
  uid = str(uuid.uuid4())[:8]
310
  html_path = f"mockup_{label.replace(' ', '_')}_{uid}.html"
311
  img_path = f"mockup_{label.replace(' ', '_')}_{uid}.png"
 
316
  return image_paths
317
 
318
  # -----------------------
319
+ # Retry Wrapper
320
  # -----------------------
321
  def generate_from_pdf(pdf_file):
322
  for attempt in range(3):
 
325
  return generate_mockups(app_title, screens)
326
  except Exception as e:
327
  if "429" in str(e) or "quota" in str(e).lower():
328
+ print("⏳ Waiting for Gemini quota reset...")
329
  time.sleep(10)
330
  else:
331
  raise e
332
+ raise Exception("❌ Failed after retries.")
333
 
334
  # -----------------------
335
  # Gradio UI