dina1 commited on
Commit
40d4548
·
verified ·
1 Parent(s): a7c6ba3

Update app.py

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