Update app.py
Browse files
app.py
CHANGED
|
@@ -165,45 +165,39 @@ SVG_FLUENT = {
|
|
| 165 |
# -----------------------
|
| 166 |
# Dynamic Top Bar Generator
|
| 167 |
# -----------------------
|
| 168 |
-
def generate_topbar(screen_name, user_name="
|
| 169 |
-
"""Generate top bar based
|
| 170 |
initials = "".join([x[0] for x in user_name.split()[:2]]).upper()
|
| 171 |
|
| 172 |
ICONS = {
|
| 173 |
-
"
|
| 174 |
-
"
|
| 175 |
-
"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>""",
|
| 176 |
-
"add": """<svg viewBox="0 0 24 24"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"/></svg>""",
|
| 177 |
-
"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>""",
|
| 178 |
-
"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>""",
|
| 179 |
-
"filter": """<svg viewBox="0 0 24 24"><path d="M10 18h4v-2h-4v2zm-7-7v2h18v-2H3zm3-5v2h12V6H6z"/></svg>"""
|
|
|
|
| 180 |
}
|
| 181 |
|
| 182 |
-
|
| 183 |
-
left_icon
|
|
|
|
| 184 |
|
| 185 |
-
if "dashboard"
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
right_icons = ICONS["filter"] + ICONS["add"]
|
| 191 |
-
elif "task" in name_lower:
|
| 192 |
-
left_icon = ICONS["menu"]
|
| 193 |
-
right_icons = ICONS["filter"]
|
| 194 |
-
elif "form" in name_lower or "edit" in name_lower or "add" in name_lower:
|
| 195 |
left_icon = ICONS["back"]
|
| 196 |
-
right_icons = ICONS["check"]
|
| 197 |
-
|
| 198 |
-
left_icon = ICONS["
|
| 199 |
-
right_icons = ICONS["
|
| 200 |
|
| 201 |
return f"""
|
| 202 |
<div class="topbar">
|
| 203 |
-
<div class="topbar-left">
|
| 204 |
-
{left_icon}
|
| 205 |
-
<span>{screen_name}</span>
|
| 206 |
-
</div>
|
| 207 |
<div class="topbar-right">
|
| 208 |
{right_icons}
|
| 209 |
<div class="topbar-user">
|
|
@@ -274,6 +268,25 @@ def analyze_business_pdf(pdf_file):
|
|
| 274 |
if (t := page.extract_text()):
|
| 275 |
text += t + "\n"
|
| 276 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
try:
|
| 278 |
app_title = model.generate_content(
|
| 279 |
"From the following business requirements, infer a concise PowerApp title. Return only the title.\n\n" + text
|
|
@@ -281,50 +294,49 @@ def analyze_business_pdf(pdf_file):
|
|
| 281 |
except Exception:
|
| 282 |
app_title = "PowerApp Solution"
|
| 283 |
|
| 284 |
-
# Ask Gemini
|
| 285 |
try:
|
| 286 |
prompt = (
|
| 287 |
-
"You are a PowerApps
|
| 288 |
-
"Return JSON in this format:\n"
|
| 289 |
"["
|
| 290 |
" {\"group\": \"Metadata\", \"screens\": ["
|
| 291 |
-
" {\"screen_name\": \"Metadata Dashboard\", \"html\": \"<div>...</div>\"}
|
| 292 |
-
" {\"screen_name\": \"Document Metadata\", \"html\": \"<div>...</div>\"}"
|
| 293 |
-
" ]},"
|
| 294 |
-
" {\"group\": \"Settings\", \"screens\": ["
|
| 295 |
-
" {\"screen_name\": \"User Preferences\", \"html\": \"<div>...</div>\"}"
|
| 296 |
" ]}"
|
| 297 |
-
"]\n
|
| 298 |
-
"
|
| 299 |
-
"\n
|
|
|
|
|
|
|
| 300 |
)
|
|
|
|
| 301 |
response = model.generate_content(prompt)
|
| 302 |
cleaned = re.sub(r"```(?:json)?|```", "", response.text.strip())
|
| 303 |
groups = eval(cleaned) if cleaned.strip().startswith("[") else []
|
|
|
|
| 304 |
except Exception as e:
|
| 305 |
print("⚠️ Gemini failed:", e)
|
| 306 |
groups = [{
|
| 307 |
"group": "My Work",
|
| 308 |
-
"screens": [{"screen_name": "Dashboard", "html": "<h2>Error</h2>"}]
|
| 309 |
}]
|
| 310 |
|
| 311 |
-
return app_title, groups
|
| 312 |
|
| 313 |
# -----------------------
|
| 314 |
# Generate Mockups
|
| 315 |
# -----------------------
|
| 316 |
-
def generate_mockups(app_title, groups):
|
| 317 |
image_paths = []
|
| 318 |
for group in groups:
|
| 319 |
for screen in group.get("screens", []):
|
| 320 |
label = screen.get("screen_name", "Screen")
|
| 321 |
html_content = screen.get("html", "<h2>Empty Screen</h2>")
|
| 322 |
sidebar_html = generate_sidebar(app_title, groups, active_label=label)
|
| 323 |
-
topbar_html = generate_topbar(label)
|
|
|
|
| 324 |
full_html = BASE_TEMPLATE.replace("{user_sidebar}", sidebar_html)\
|
| 325 |
.replace("{user_content}", html_content)\
|
| 326 |
.replace("{user_topbar}", topbar_html)
|
| 327 |
-
|
| 328 |
uid = str(uuid.uuid4())[:8]
|
| 329 |
html_path = f"mockup_{label.replace(' ', '_')}_{uid}.html"
|
| 330 |
img_path = f"mockup_{label.replace(' ', '_')}_{uid}.png"
|
|
@@ -340,15 +352,15 @@ def generate_mockups(app_title, groups):
|
|
| 340 |
def generate_from_pdf(pdf_file):
|
| 341 |
for attempt in range(3):
|
| 342 |
try:
|
| 343 |
-
app_title,
|
| 344 |
-
return generate_mockups(app_title,
|
| 345 |
except Exception as e:
|
| 346 |
if "429" in str(e) or "quota" in str(e).lower():
|
| 347 |
-
print("⏳ Waiting for Gemini quota reset...")
|
| 348 |
time.sleep(10)
|
| 349 |
else:
|
| 350 |
raise e
|
| 351 |
-
raise Exception("❌ Failed after retries.")
|
| 352 |
|
| 353 |
# -----------------------
|
| 354 |
# Gradio UI
|
|
|
|
| 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>""",
|
| 177 |
+
"check": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M9 16.17 4.83 12l-1.42 1.41L9 19l12-12-1.41-1.41z"/></svg>""",
|
| 178 |
+
"info": """<svg viewBox="0 0 24 24"><path fill="#fff" 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>""",
|
| 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">
|
|
|
|
| 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
|
|
|
|
| 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"
|
|
|
|
| 352 |
def generate_from_pdf(pdf_file):
|
| 353 |
for attempt in range(3):
|
| 354 |
try:
|
| 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
|