dina1 commited on
Commit
4dc83f5
·
verified ·
1 Parent(s): 3dd0623

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -282
app.py CHANGED
@@ -1,381 +1,233 @@
1
- import os
2
- import re
3
- import json
4
- import uuid
5
- import pdfplumber
6
  import google.generativeai as genai
7
  import gradio as gr
8
  from html2image import Html2Image
 
 
 
 
 
9
 
10
  # -----------------------
11
- # Configure Gemini
12
  # -----------------------
13
- genai.configure(api_key=os.environ["GEMINI_API_KEY"])
14
  model = genai.GenerativeModel("gemini-2.5-pro")
15
 
16
  # -----------------------
17
- # Ensure headless chromium exists (Spaces friendly)
18
  # -----------------------
19
  hti = Html2Image(browser_executable="/usr/bin/chromium")
20
 
21
  # -----------------------
22
- # BASE HTML/CSS tuned to uploaded PowerApps mockups
23
- # (colors, sizes, active-left-accent, compact spacing)
24
  # -----------------------
25
  BASE_TEMPLATE = """
26
  <html>
27
  <head>
28
- <meta charset="utf-8">
29
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
30
  <style>
31
- :root{
32
- --sidebar-bg:#2b2b2b; --sidebar-section:#1f1f1f; --muted:#bfbfbf; --accent:#0078d4;
33
- --main-bg:#f3f3f3; --card-bg:#ffffff;
34
- }
35
- *{box-sizing:border-box}
36
  body {
37
  margin: 0;
38
- font-family: 'Segoe UI', Roboto, Arial, sans-serif;
39
- background: var(--main-bg);
40
- color: #222;
41
- -webkit-font-smoothing:antialiased;
42
  }
43
  .sidebar {
44
  width: 240px;
45
  height: 100vh;
46
- background: var(--sidebar-bg);
47
  color: #fff;
48
  float: left;
49
  display: flex;
50
  flex-direction: column;
51
- border-right: 1px solid rgba(255,255,255,0.03);
52
  }
53
  .sidebar-header {
54
  display: flex;
55
  align-items: center;
56
- gap: 10px;
57
- padding: 12px 14px;
58
- font-size: 14px;
59
- font-weight: 600;
60
- background: var(--sidebar-section);
61
  }
62
- .sidebar-header svg { width: 18px; height:18px; flex-shrink:0; }
63
- .app-title-mini { font-size:13px; color:#fff; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
64
-
65
  .sidebar-item {
66
- padding: 10px 14px;
67
  cursor: pointer;
68
  display: flex;
69
  align-items: center;
70
- gap: 10px;
71
- transition: background 0.12s ease;
72
- position: relative;
73
  }
74
- .sidebar-item svg { width: 14px; height:14px; min-width:14px; min-height:14px; fill: var(--muted); flex-shrink:0; }
75
- .sidebar-item span { color: var(--muted); font-size:13px; }
76
- .sidebar-item:hover { background: rgba(255,255,255,0.02); }
77
- .sidebar-item.active { background: rgba(0,0,0,0.06); color: #fff; }
78
- .sidebar-item.active span { color: #fff; font-weight:600; }
79
- .sidebar-item.active::before {
80
- content: "";
81
- position: absolute;
82
- left: 0;
83
- top: 6px;
84
- bottom: 6px;
85
- width: 4px;
86
- background: var(--accent);
87
- border-top-right-radius:4px;
88
- border-bottom-right-radius:4px;
 
89
  }
90
-
91
- /* section header small uppercase */
92
  .sidebar-section {
93
- margin-top: 12px;
94
- padding: 10px 14px;
95
- font-size: 11px;
96
- font-weight:700;
97
- text-transform:uppercase;
98
- color: #a7a7a7;
99
- letter-spacing:0.6px;
100
  }
101
-
102
- /* My Work nested / indented look */
103
- .sidebar-item.indent { padding-left: 30px; font-size:13px; }
104
-
105
- /* topbar */
106
  .topbar {
107
- height: 52px;
108
- background: var(--accent);
109
  color: #fff;
110
  display: flex;
111
  align-items: center;
112
  padding: 0 20px;
113
- font-weight: 600;
114
- font-size: 15px;
115
- position: sticky;
116
- top:0;
117
  }
118
-
119
- /* main area */
120
  .main {
121
  margin-left: 240px;
122
- padding: 18px;
123
  }
124
-
125
- /* content cards */
126
  .card-grid {
127
  display: grid;
128
- grid-template-columns: repeat(auto-fit,minmax(240px,1fr));
129
- gap: 14px;
130
  }
131
  .card {
132
- background: var(--card-bg);
133
  border-radius: 8px;
134
- padding: 12px;
135
- box-shadow: 0 1px 6px rgba(0,0,0,0.06);
136
- border: 1px solid rgba(0,0,0,0.03);
137
  }
138
- .card h3 { margin:0 0 8px 0; font-size:14px; }
139
- .card p { margin:0; color:#444; font-size:13px; }
140
-
141
- /* KPI chart container */
142
- .chart-container { width:100%; height:220px; }
143
-
144
- /* table */
145
- .table-container { margin-top:10px; overflow-x:auto; }
146
- table { width:100%; border-collapse:collapse; font-size:13px; }
147
- th,td { padding:8px 10px; border-bottom:1px solid #eee; text-align:left; }
148
- th { background:#fafafa; font-weight:600; font-size:12px; color:#333; }
149
-
150
- /* right spacing for large views */
151
- .page-row { display:flex; gap:14px; align-items:flex-start; margin-bottom:14px; }
152
- .page-col { flex:1; min-width:220px; }
153
-
154
- /* small helper */
155
- .small-muted { color:#777; font-size:12px; }
156
  </style>
157
  </head>
158
  <body>
159
  {user_sidebar}
160
  <div class="topbar">{app_title}</div>
161
- <div class="main">
162
- {user_content}
163
- </div>
164
-
165
- <script>
166
- {chart_js}
167
- </script>
168
  </body>
169
  </html>
170
  """
171
 
172
  # -----------------------
173
- # Basic fluent icons used as defaults (small/compact)
174
  # -----------------------
175
  SVG_FLUENT = {
176
- "hamburger": """<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z"/></svg>""",
177
- "home": """<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>""",
178
- "recent": """<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M12 8v5l4 2 .7-1.2-3.2-1.8V8h-1.5zM12 2a10 10 0 100 20 10 10 0 000-20z"/></svg>""",
179
- "pinned": """<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M14 2v2l2 2v3l2 2v2H6v-2l2-2V6l2-2V2h4z"/></svg>""",
180
  }
181
 
182
  # -----------------------
183
- # Extract text from PDF
184
  # -----------------------
185
- def extract_pdf_text(pdf_path):
186
- text = ""
187
- with pdfplumber.open(pdf_path) as pdf:
188
- for p in pdf.pages:
189
- page_text = p.extract_text()
190
- if page_text:
191
- text += page_text + "\n"
192
- return text.strip()
193
 
194
- # -----------------------
195
- # Ask Gemini for structured sidebar and which item is active
196
- # This endpoint will ask for JSON: [{label, icon_svg, active}]
197
- # -----------------------
198
- def infer_sidebar_items(ui_description):
199
- prompt = (
200
- "You are a PowerApps UI designer. Based on the following app description, "
201
- "return a JSON array of 3-6 'My Work' sidebar items. For each item provide:\n"
202
- " - label (short string),\n"
203
- " - icon (a minimal Fluent-style SVG string with fill '#c5c5c5'),\n"
204
- " - active (true/false)\n"
205
- "Return only JSON. Example:\n"
206
- "[{\"label\":\"Dashboard\",\"icon\":\"<svg ...></svg>\",\"active\":true}, ...]"
 
207
  )
208
- resp = model.generate_content(prompt + "\n\n" + ui_description)
209
- json_text = re.sub(r"```(?:json)?|```", "", resp.text.strip())
210
  try:
 
 
 
211
  items = json.loads(json_text)
212
- except Exception:
213
- # fallback if model output not parseable
214
  items = [
215
- {"label": "Dashboard", "icon": "<svg viewBox='0 0 24 24'><circle cx='12' cy='12' r='6' fill='#c5c5c5'/></svg>", "active": True},
216
- {"label": "Requests", "icon": "<svg viewBox='0 0 24 24'><rect x='4' y='4' width='16' height='12' fill='#c5c5c5'/></svg>", "active": False},
217
- {"label": "Templates", "icon": "<svg viewBox='0 0 24 24'><path d='M4 6h16v12H4z' fill='#c5c5c5'/></svg>", "active": False}
218
  ]
219
- return items
220
 
221
- # -----------------------
222
- # Ask Gemini for KPIs: chart/table JSON
223
- # -----------------------
224
- def infer_kpis(ui_description):
225
- prompt = (
226
- "From the app/business description below, extract 2-4 KPIs and return JSON array where each item is either:\n"
227
- " - {\"type\":\"chart\",\"title\":\"...\",\"chart_type\":\"bar|line|pie\",\"labels\":[...],\"values\":[...]}\n"
228
- " - {\"type\":\"table\",\"title\":\"...\",\"columns\":[...],\"rows\":[[...],[...]]}\n"
229
- "Return only JSON."
230
- )
231
- resp = model.generate_content(prompt + "\n\n" + ui_description)
232
- json_text = re.sub(r"```(?:json)?|```", "", resp.text.strip())
233
- try:
234
- kpis = json.loads(json_text)
235
- except Exception:
236
- kpis = [{"type":"chart","title":"Sample KPI","chart_type":"bar","labels":["Jan","Feb","Mar"],"values":[30,45,28]}]
237
- return kpis
238
 
239
  # -----------------------
240
- # Ask Gemini for main content skeleton (cards, short description)
241
  # -----------------------
242
- def infer_main_content(ui_description):
243
- prompt = (
244
- "Based on this inferred app description, produce a short HTML fragment (no full page) "
245
- "that contains a short hero/summary area and placeholders for KPI cards. Keep it concise."
 
 
 
 
 
 
 
 
246
  )
247
- resp = model.generate_content(prompt + "\n\n" + ui_description)
248
- html = re.sub(r"```(?:html)?|```", "", resp.text.strip())
249
- # If model returns plain text, create a tiny hero
250
- if "<" not in html:
251
- html = f"<div class='card'><h3>Summary</h3><p class='small-muted'>{html}</p></div>"
252
- return html
253
 
254
- # -----------------------
255
- # Build sidebar HTML (static defaults + dynamic My Work items)
256
- # -----------------------
257
- def build_sidebar(app_title, ui_description):
258
- items = infer_sidebar_items(ui_description)
259
- html = "<div class='sidebar'>"
260
- html += f"<div class='sidebar-header'>{SVG_FLUENT['hamburger']}<div class='app-title-mini'>{app_title}</div></div>"
261
- # defaults
262
- html += f"<div class='sidebar-item'>{SVG_FLUENT['home']}<span>Home</span></div>"
263
- html += f"<div class='sidebar-item'>{SVG_FLUENT['recent']}<span>Recent</span></div>"
264
- html += f"<div class='sidebar-item'>{SVG_FLUENT['pinned']}<span>Pinned</span></div>"
265
- html += "<div class='sidebar-section'>My Work</div>"
266
- for it in items:
267
- active = "active" if it.get("active") else ""
268
- # ensure icon exists
269
- icon = it.get("icon") or "<svg viewBox='0 0 24 24'><circle cx='12' cy='12' r='5' fill='#c5c5c5'/></svg>"
270
- label = it.get("label","Item")
271
- # indent small items like subsections if label contains colon or dash (simple heuristic)
272
- indent_class = " indent" if label.lower().startswith(("•","-")) or label.startswith(" ") else ""
273
- html += f"<div class='sidebar-item {active}{indent_class}'>{icon}<span>{label}</span></div>"
274
- html += "</div>"
275
- return html
276
 
277
- # -----------------------
278
- # Render KPI HTML + Chart.js script
279
- # -----------------------
280
- def render_kpis(kpis):
281
- html_parts = []
282
- js_parts = []
283
- for idx,k in enumerate(kpis):
284
- if k.get("type") == "chart":
285
- cid = f"chart_{idx}"
286
- title = k.get("title","KPI")
287
- chart_type = k.get("chart_type","bar")
288
- labels = json.dumps(k.get("labels",[]))
289
- values = json.dumps(k.get("values",[]))
290
- html_parts.append(f"<div class='card'><h3>{title}</h3><div class='chart-container'><canvas id='{cid}'></canvas></div></div>")
291
- js = f"""
292
- new Chart(document.getElementById('{cid}'), {{
293
- type: '{chart_type}',
294
- data: {{
295
- labels: {labels},
296
- datasets: [{{
297
- label: '{title}',
298
- data: {values},
299
- backgroundColor: 'rgba(0,120,212,0.6)',
300
- borderColor: 'rgba(0,120,212,0.9)',
301
- borderWidth: 1
302
- }}]
303
- }},
304
- options: {{ responsive: true, maintainAspectRatio: false, plugins: {{ legend: {{ display: false }} }} }}
305
- }});
306
- """
307
- js_parts.append(js)
308
- elif k.get("type") == "table":
309
- title = k.get("title","Table")
310
- cols = k.get("columns",[])
311
- rows = k.get("rows",[])
312
- table_html = f"<div class='card table-container'><h3>{title}</h3><table><tr>"
313
- for c in cols: table_html += f"<th>{c}</th>"
314
- table_html += "</tr>"
315
- for r in rows:
316
- table_html += "<tr>" + "".join(f"<td>{cell}</td>" for cell in r) + "</tr>"
317
- table_html += "</table></div>"
318
- html_parts.append(table_html)
319
- return "\n".join(html_parts), "\n".join(js_parts)
320
 
321
  # -----------------------
322
- # Full generation pipeline
323
  # -----------------------
324
- def generate_mockup_from_pdf(pdf_file):
325
- # 1) extract text
326
- text = extract_pdf_text(pdf_file.name)
327
-
328
- # 2) infer app title
329
- title_prompt = "Read the following business document and reply with a concise PowerApps-style app title (one short line). Return only the title."
330
- try:
331
- app_title = model.generate_content(title_prompt + "\n\n" + text).text.strip()
332
- if not app_title:
333
- app_title = "AI Generated App"
334
- except Exception:
335
- app_title = "AI Generated App"
336
-
337
- # 3) infer overall UI description (plain English)
338
- ui_prompt = "You are an expert PowerApps architect. From the text below, describe in plain English the app's screens, main components, and what sorts of KPIs/charts/tables would be useful."
339
- try:
340
- ui_text = model.generate_content(ui_prompt + "\n\n" + text).text.strip()
341
- except Exception:
342
- ui_text = "Dashboard with pending requests and summary KPIs."
343
-
344
- # 4) infer KPIs JSON
345
- kpis = infer_kpis(ui_text)
346
-
347
- # 5) build sidebar
348
- sidebar_html = build_sidebar(app_title, ui_text)
349
-
350
- # 6) main content skeleton
351
- main_frag = infer_main_content(ui_text)
352
-
353
- # 7) render kpis
354
- kpi_html, kpi_js = render_kpis(kpis)
355
 
356
- # combine content — keep model-produced skeleton and attach KPIs
357
- user_content = f"{main_frag}\n<div class='page-row'>{kpi_html}</div>"
358
 
359
- full_html = BASE_TEMPLATE.format(
360
- user_sidebar=sidebar_html,
361
- app_title=app_title,
362
- user_content=user_content,
363
- chart_js=kpi_js
364
- )
365
 
366
- # write and snapshot
367
- uid = uuid.uuid4().hex
368
- html_path = f"mockup_{uid}.html"
369
- img_path = f"mockup_{uid}.png"
370
- with open(html_path, "w", encoding="utf-8") as fh:
371
- fh.write(full_html)
372
 
373
- # render (Html2Image can take html_file)
374
  hti.screenshot(html_file=html_path, save_as=img_path)
375
  return img_path
376
 
377
  # -----------------------
378
- # Gradio app
 
 
 
 
 
 
 
 
379
  # -----------------------
380
  with gr.Blocks() as demo:
381
  gr.Markdown("## 🧠 Intelligent PowerApps Mockup Generator (PDF → UI)")
 
 
 
 
 
 
1
  import google.generativeai as genai
2
  import gradio as gr
3
  from html2image import Html2Image
4
+ import pdfplumber
5
+ import uuid
6
+ import os
7
+ import re
8
+ import json
9
 
10
  # -----------------------
11
+ # Configure Gemini API
12
  # -----------------------
13
+ genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
14
  model = genai.GenerativeModel("gemini-2.5-pro")
15
 
16
  # -----------------------
17
+ # Html2Image Setup
18
  # -----------------------
19
  hti = Html2Image(browser_executable="/usr/bin/chromium")
20
 
21
  # -----------------------
22
+ # Base PowerApps Layout
 
23
  # -----------------------
24
  BASE_TEMPLATE = """
25
  <html>
26
  <head>
 
 
27
  <style>
 
 
 
 
 
28
  body {
29
  margin: 0;
30
+ font-family: 'Segoe UI', sans-serif;
31
+ background: #f5f5f5;
 
 
32
  }
33
  .sidebar {
34
  width: 240px;
35
  height: 100vh;
36
+ background: #2d2d2d;
37
  color: #fff;
38
  float: left;
39
  display: flex;
40
  flex-direction: column;
 
41
  }
42
  .sidebar-header {
43
  display: flex;
44
  align-items: center;
45
+ padding: 12px 16px;
46
+ font-size: 16px;
47
+ font-weight: bold;
48
+ background: #1f1f1f;
 
49
  }
 
 
 
50
  .sidebar-item {
51
+ padding: 10px 18px;
52
  cursor: pointer;
53
  display: flex;
54
  align-items: center;
55
+ transition: background 0.2s ease;
 
 
56
  }
57
+ .sidebar-item svg {
58
+ width: 16px;
59
+ height: 16px;
60
+ min-width: 16px;
61
+ min-height: 16px;
62
+ margin-right: 8px;
63
+ fill: #c5c5c5;
64
+ flex-shrink: 0;
65
+ }
66
+ .sidebar-item span {
67
+ color: #ccc;
68
+ }
69
+ .sidebar-item:hover { background: #3a3a3a; }
70
+ .sidebar-item.active {
71
+ background: #0078d4;
72
+ color: #fff;
73
  }
 
 
74
  .sidebar-section {
75
+ margin-top: 14px;
76
+ padding: 8px 18px;
77
+ font-size: 12px;
78
+ font-weight: bold;
79
+ text-transform: uppercase;
80
+ color: #aaa;
 
81
  }
 
 
 
 
 
82
  .topbar {
83
+ height: 50px;
84
+ background: #0078d4;
85
  color: #fff;
86
  display: flex;
87
  align-items: center;
88
  padding: 0 20px;
89
+ font-weight: bold;
 
 
 
90
  }
 
 
91
  .main {
92
  margin-left: 240px;
93
+ padding: 20px;
94
  }
 
 
95
  .card-grid {
96
  display: grid;
97
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
98
+ gap: 15px;
99
  }
100
  .card {
101
+ background: #fff;
102
  border-radius: 8px;
103
+ padding: 15px;
104
+ box-shadow: 0 1px 4px rgba(0,0,0,0.1);
 
105
  }
106
+ .status.active { color: green; font-weight: bold; }
107
+ .status.inactive { color: red; font-weight: bold; }
108
+ .chart-container { margin: 20px 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  </style>
110
  </head>
111
  <body>
112
  {user_sidebar}
113
  <div class="topbar">{app_title}</div>
114
+ <div class="main">{user_content}</div>
 
 
 
 
 
 
115
  </body>
116
  </html>
117
  """
118
 
119
  # -----------------------
120
+ # Default Fluent Icons
121
  # -----------------------
122
  SVG_FLUENT = {
123
+ "hamburger": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z"/></svg>""",
124
+ "home": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>""",
125
+ "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>""",
126
+ "pinned": """<svg viewBox="0 0 24 24"><path fill="#fff" d="M14 2v2l2 2v3l2 2v2H6v-2l2-2V6l2-2V2h4zm-1 13v7h-2v-7h2z"/></svg>"""
127
  }
128
 
129
  # -----------------------
130
+ # Generate Sidebar (Dynamic + Static)
131
  # -----------------------
132
+ def generate_sidebar(ui_description, app_title):
133
+ sidebar_html = "<div class='sidebar'>"
134
+ sidebar_html += f"<div class='sidebar-header'>{SVG_FLUENT['hamburger']} {app_title}</div>"
 
 
 
 
 
135
 
136
+ # Default items
137
+ defaults = [("home", "Home"), ("recent", "Recent"), ("pinned", "Pinned")]
138
+ for key, label in defaults:
139
+ sidebar_html += f"<div class='sidebar-item'>{SVG_FLUENT[key]}<span>{label}</span></div>"
140
+
141
+ sidebar_html += "<div class='sidebar-section'>My Work</div>"
142
+
143
+ # Ask Gemini to produce structured JSON for dynamic sidebar
144
+ sidebar_prompt = (
145
+ "You are a PowerApps UI designer. Based on the inferred app description below, "
146
+ "generate 3–6 realistic items for the 'My Work' section. "
147
+ "For each item, provide: label, a minimal SVG icon (Fluent-style, color #c5c5c5), "
148
+ "and specify one as active (true). Return only JSON in this format:\n\n"
149
+ "[{\"label\": \"Dashboard\", \"icon\": \"<svg...>\", \"active\": true}, ...]"
150
  )
151
+
 
152
  try:
153
+ response = model.generate_content(sidebar_prompt + "\n\n" + ui_description)
154
+ text = response.text.strip()
155
+ json_text = re.sub(r"```(?:json)?|```", "", text)
156
  items = json.loads(json_text)
157
+ except Exception as e:
 
158
  items = [
159
+ {"label": "Dashboard", "icon": "<svg viewBox='0 0 24 24'><circle cx='12' cy='12' r='5' fill='#c5c5c5'/></svg>", "active": True},
160
+ {"label": "Requests", "icon": "<svg viewBox='0 0 24 24'><rect x='4' y='4' width='16' height='16' fill='#c5c5c5'/></svg>", "active": False},
 
161
  ]
 
162
 
163
+ for item in items:
164
+ active_class = "active" if item.get("active") else ""
165
+ sidebar_html += f"<div class='sidebar-item {active_class}'>{item['icon']}<span>{item['label']}</span></div>"
166
+
167
+ sidebar_html += "</div>"
168
+ return sidebar_html
 
 
 
 
 
 
 
 
 
 
 
169
 
170
  # -----------------------
171
+ # Analyze Business PDF Infer App UI
172
  # -----------------------
173
+ def analyze_business_pdf(pdf_file):
174
+ text = ""
175
+ with pdfplumber.open(pdf_file.name) as pdf:
176
+ for page in pdf.pages:
177
+ page_text = page.extract_text()
178
+ if page_text:
179
+ text += page_text + "\n"
180
+
181
+ # Extract app title
182
+ title_prompt = (
183
+ "Read the following business requirements and infer the most appropriate PowerApp title "
184
+ "(e.g., 'Employee Leave Tracker', 'Sales Dashboard'). Return only the title text."
185
  )
186
+ app_title = model.generate_content(title_prompt + "\n\n" + text).text.strip()
 
 
 
 
 
187
 
188
+ inference_prompt = (
189
+ "You are an expert PowerApps architect. Analyze the following business requirements "
190
+ "and infer what kind of PowerApps solution is needed. "
191
+ "Identify screens, forms, charts, and navigation ideas in plain English."
192
+ )
193
+ ui_text = model.generate_content(inference_prompt + "\n\n" + text).text.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
+ return app_title, ui_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
  # -----------------------
198
+ # Generate Full Mockup
199
  # -----------------------
200
+ def generate_mockup(app_title, ui_text):
201
+ ui_prompt = (
202
+ "You are a PowerApps UI generator. Based on this inferred app description, "
203
+ "generate only the inner HTML for main content — include cards, forms, charts, or tables. "
204
+ "Use realistic placeholder data and PowerApps-style components."
205
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
+ response = model.generate_content(ui_prompt + "\n\n" + ui_text)
208
+ user_html = re.sub(r"```(?:html)?|```", "", response.text.strip())
209
 
210
+ sidebar_html = generate_sidebar(ui_text, app_title)
211
+ full_html = BASE_TEMPLATE.replace("{user_sidebar}", sidebar_html).replace("{user_content}", user_html).replace("{app_title}", app_title)
 
 
 
 
212
 
213
+ uid = str(uuid.uuid4())
214
+ html_path, img_path = f"mockup_{uid}.html", f"mockup_{uid}.png"
215
+ with open(html_path, "w", encoding="utf-8") as f:
216
+ f.write(full_html)
 
 
217
 
 
218
  hti.screenshot(html_file=html_path, save_as=img_path)
219
  return img_path
220
 
221
  # -----------------------
222
+ # Full Pipeline
223
+ # -----------------------
224
+ def generate_from_pdf(pdf_file):
225
+ app_title, ui_text = analyze_business_pdf(pdf_file)
226
+ mockup_img = generate_mockup(app_title, ui_text)
227
+ return mockup_img
228
+
229
+ # -----------------------
230
+ # Gradio UI
231
  # -----------------------
232
  with gr.Blocks() as demo:
233
  gr.Markdown("## 🧠 Intelligent PowerApps Mockup Generator (PDF → UI)")