Ritvik Shrivastava commited on
Commit
b4e9a74
Β·
1 Parent(s): d3c46a0

minor changes pt3!

Browse files
Files changed (1) hide show
  1. app.py +136 -285
app.py CHANGED
@@ -1,326 +1,177 @@
1
  """
2
- app.py β€” GharScan HuggingFace Space (Improved UI)
 
3
  """
 
4
  import spaces
5
  import gradio as gr
6
  from PIL import Image
 
 
7
  from inference import run_gharscan_pipeline
8
  from agent_trace import AgentTraceLogger
9
 
10
  trace_logger = AgentTraceLogger()
11
 
 
12
  @spaces.GPU
13
- def analyze_image(image: Image.Image) -> str:
14
  if image is None:
15
- return "<p style='color:#6b7280;padding:24px;text-align:center'>πŸ“· Please upload or photograph a wall defect first.</p>"
16
  session = trace_logger.start_trace()
17
- report = run_gharscan_pipeline(image, language="en", trace_session=session)
18
  trace_logger.save_trace(session)
19
- return _render_report(report)
20
-
21
 
22
- def _render_report(r: dict) -> str:
 
 
 
23
  if not r.get("analysis_ok"):
24
- desc = r.get("description", "Could not analyse the image.")
25
- return f"""
26
- <div style='background:#1a1d23;border:1px solid #2a2f38;border-radius:12px;
27
- padding:24px;text-align:center;color:#9ca3af;font-family:Inter,sans-serif'>
28
- <div style='font-size:36px;margin-bottom:12px'>πŸ”</div>
29
- <p style='font-weight:600;color:#e8eaed;margin-bottom:6px'>No defect detected</p>
30
- <p style='font-size:13px'>{desc or 'Try photographing the defect area more closely with good lighting.'}</p>
 
 
 
31
  </div>"""
 
 
32
 
33
- sev = r.get("severity", 1)
34
- color = r.get("severity_color", "#22c55e")
35
- pct = sev * 20
36
-
37
- struct = (f"""<div style='background:rgba(127,29,29,0.3);border:1px solid #7f1d1d;
38
- border-radius:10px;padding:13px 16px;margin-bottom:14px;
39
- display:flex;gap:10px;align-items:flex-start;color:#fca5a5;font-size:13px'>
40
- <span style='font-size:18px;flex-shrink:0'>⚠️</span>
41
- <div><strong style='font-size:14px'>STRUCTURAL RISK</strong>
42
- <br><span style='color:#f87171'>{r.get("structural_reasoning","")}</span></div></div>"""
43
- if r.get("is_structural") else
44
- """<div style='background:rgba(21,128,61,0.2);border:1px solid #166534;
45
- border-radius:10px;padding:11px 16px;margin-bottom:14px;
46
- color:#86efac;font-size:13px;display:flex;align-items:center;gap:8px'>
47
- <span>βœ…</span><strong>Not Structural β€” No immediate safety risk</strong></div>""")
48
 
49
- monsoon = ("""<div style='background:rgba(234,179,8,0.12);border:1px solid rgba(234,179,8,0.35);
50
- border-radius:8px;padding:10px 14px;color:#fde68a;font-size:13px;margin-top:10px'>
51
- 🌧️ <strong>Monsoon Risk</strong> β€” This defect worsens in heavy rain. Act before June.</div>"""
52
- if r.get("monsoon_risk") else "")
53
 
54
- liability = (f"""<div style='background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);
55
- border-radius:10px;padding:12px 14px;font-size:12px;color:#fca5a5;
56
- margin-top:10px;display:flex;gap:8px'>
57
- <span style='flex-shrink:0'>⚠️</span><p>{r.get("liability_text","")}</p></div>"""
58
- if r.get("show_liability_banner") else "")
59
-
60
- disclaimer = (f"""<div style='border-left:3px solid #f59e0b;padding:10px 14px;
61
- font-size:12px;color:#9ca3af;margin-top:8px;background:#1a1d23;
62
- border-radius:0 8px 8px 0'>{r["disclaimer"]}</div>"""
63
- if r.get("disclaimer") else "")
64
 
65
  return f"""
66
- <div style='font-family:Inter,sans-serif;color:#e8eaed;max-width:640px'>
67
- {struct}
68
-
69
- <div style='background:#181b20;border:1px solid #2a2f38;border-radius:14px;
70
- padding:20px;box-shadow:0 4px 16px rgba(0,0,0,0.5)'>
71
-
72
- <!-- Header row -->
73
- <div style='display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px;gap:12px'>
74
- <div>
75
- <div style='font-size:20px;font-weight:700;line-height:1.2'>{r.get("defect_display","")}</div>
76
- <div style='font-size:11px;color:#6b7280;margin-top:4px;font-family:monospace;letter-spacing:.05em'>
77
- {r.get("defect_type","").replace("_"," ").upper()}</div>
78
- </div>
79
- <div style='background:#20242b;border:2px solid {color};border-radius:12px;
80
- padding:8px 14px;text-align:center;flex-shrink:0;min-width:72px'>
81
- <div style='font-size:26px;font-weight:700;color:{color};font-family:monospace;line-height:1'>{sev}</div>
82
- <div style='font-size:10px;color:#8b9099;letter-spacing:.06em;margin-top:2px'>
83
- {r.get("severity_label","").upper()}</div>
84
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  </div>
 
86
 
87
- <!-- Severity bar -->
88
- <div style='background:#1a1d23;border-radius:4px;height:8px;margin-bottom:5px;overflow:hidden'>
89
- <div style='width:{pct}%;height:8px;border-radius:4px;background:{color};
90
- transition:width 0.6s ease'></div>
91
- </div>
92
- <div style='display:flex;justify-content:space-between;font-size:10px;
93
- color:#4b5563;font-family:monospace;margin-bottom:18px'>
94
- <span>Cosmetic</span><span>Moderate</span><span>Critical</span>
95
- </div>
96
-
97
- <div style='height:1px;background:#1e2229;margin-bottom:16px'></div>
98
-
99
- <!-- Description rows -->
100
- <div style='margin-bottom:12px'>
101
- <div style='font-size:10px;color:#6fb3e0;font-family:monospace;letter-spacing:.08em;
102
- margin-bottom:4px'>WHAT IT IS</div>
103
- <div style='font-size:14px;line-height:1.5'>{r.get("description","")}</div>
104
- </div>
105
- <div style='margin-bottom:12px'>
106
- <div style='font-size:10px;color:#6fb3e0;font-family:monospace;letter-spacing:.08em;
107
- margin-bottom:4px'>WHY IT HAPPENS</div>
108
- <div style='font-size:14px;line-height:1.5'>{r.get("primary_cause","")}</div>
109
- </div>
110
-
111
- <div style='height:1px;background:#1e2229;margin-bottom:16px'></div>
112
-
113
- <!-- Action block -->
114
- <div style='background:rgba(59,130,246,0.09);border:1px solid rgba(59,130,246,0.25);
115
- border-radius:10px;padding:13px 15px;margin-bottom:12px'>
116
- <div style='font-size:10px;color:#6fb3e0;font-family:monospace;
117
- letter-spacing:.08em;margin-bottom:5px'>WHAT TO DO</div>
118
- <div style='font-size:14px;font-weight:500;color:#93c5fd;line-height:1.5'>
119
- {r.get("immediate_action","Consult a professional.")}</div>
120
- </div>
121
- <div style='margin-bottom:16px'>
122
- <div style='font-size:10px;color:#6fb3e0;font-family:monospace;
123
- letter-spacing:.08em;margin-bottom:4px'>WHEN TO ACT</div>
124
- <div style='font-size:14px;font-weight:500'>{r.get("urgency_display","")}</div>
125
- </div>
126
-
127
- <div style='height:1px;background:#1e2229;margin-bottom:16px'></div>
128
-
129
- <!-- Cost block -->
130
- <div style='background:#20242b;border-radius:12px;padding:16px'>
131
- <div style='font-size:26px;font-weight:700;color:#22c55e;
132
- font-family:monospace;margin-bottom:6px'>{r.get("cost_range_inr","")}</div>
133
- <div style='font-size:13px;color:#8b9099'>
134
- πŸ‘· {r.get("professional_display","")}</div>
135
- </div>
136
-
137
- {monsoon}
138
- </div>
139
- {liability}
140
- {disclaimer}
141
- </div>"""
142
-
143
-
144
- # ── CSS ────────────────────────────────────────────────────────────────────────
145
  CSS = """
146
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
147
-
148
- *, *::before, *::after { box-sizing: border-box; }
149
- body, .gradio-container {
150
- background: #0c0e12 !important;
151
- color: #e8eaed !important;
152
- font-family: 'Inter', system-ui, sans-serif !important;
153
- }
154
- .gradio-container { max-width: 680px !important; margin: 0 auto !important; padding: 0 !important; }
155
- .main { padding: 0 16px 60px !important; }
156
- footer, .footer { display: none !important; }
157
-
158
- /* Image upload zone */
159
- .image-upload-zone {
160
- border: 2px dashed #2a2f38 !important;
161
- border-radius: 14px !important;
162
- background: #181b20 !important;
163
- min-height: 280px !important;
164
- transition: border-color 0.2s !important;
165
- }
166
- .image-upload-zone:hover { border-color: #3b82f6 !important; }
167
- .image-upload-zone .upload-text { color: #6b7280 !important; }
168
- .image-upload-zone svg { color: #374151 !important; }
169
-
170
- /* Analyse button */
171
- button.primary {
172
- background: linear-gradient(135deg, #2563eb, #1d4ed8) !important;
173
- border: none !important;
174
- border-radius: 10px !important;
175
- font-size: 15px !important;
176
- font-weight: 600 !important;
177
- padding: 14px !important;
178
- letter-spacing: 0.02em !important;
179
- box-shadow: 0 4px 14px rgba(37,99,235,0.4) !important;
180
- transition: opacity 0.15s, transform 0.1s !important;
181
- }
182
- button.primary:hover:not(:disabled) { opacity: 0.9 !important; transform: translateY(-1px) !important; }
183
- button.primary:disabled { opacity: 0.35 !important; box-shadow: none !important; }
184
-
185
- /* Output HTML area */
186
- .output-html { background: transparent !important; padding: 0 !important; }
187
-
188
- /* Examples */
189
- .examples-holder { margin-top: 4px !important; }
190
- .examples-holder table { background: #181b20 !important; border-radius: 10px !important; }
191
- .examples-holder td { padding: 6px !important; }
192
- .examples-holder img { border-radius: 6px !important; }
193
-
194
- /* Radio buttons hidden (language removed) */
195
- .gr-radio { display: none !important; }
196
-
197
- /* Labels */
198
- label.block, .block > label { color: #6b7280 !important; font-size: 12px !important; font-family: monospace !important; letter-spacing: .06em !important; }
199
-
200
- /* Scrollbar */
201
- ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: #0c0e12; }
202
- ::-webkit-scrollbar-thumb { background: #2a2f38; border-radius: 3px; }
203
- """
204
-
205
- HEADER = """
206
- <div style='display:flex;align-items:center;justify-content:space-between;
207
- padding:16px 0 18px;border-bottom:1px solid #1e2229;margin-bottom:20px'>
208
- <div style='display:flex;align-items:center;gap:12px'>
209
- <span style='font-size:30px'>πŸ—οΈ</span>
210
- <div>
211
- <div style='font-size:20px;font-weight:700;color:#e8eaed;letter-spacing:-0.3px'>GharScan</div>
212
- <div style='font-size:11px;color:#6b7280;margin-top:1px'>AI Building Defect Inspector Β· India</div>
213
- </div>
214
- </div>
215
- <div style='display:flex;flex-direction:column;align-items:flex-end;gap:6px'>
216
- <div style='background:#0d1f0d;border:1px solid #166534;border-radius:20px;
217
- padding:4px 12px;font-size:11px;color:#86efac;font-family:monospace;
218
- display:flex;align-items:center;gap:6px'>
219
- <span style='width:6px;height:6px;border-radius:50%;background:#22c55e;
220
- animation:pulse 2s infinite;display:inline-block'></span>
221
- ZeroGPU Β· Online
222
- </div>
223
- <div style='font-size:10px;color:#374151;font-family:monospace'>Qwen2-VL-2B Β· 2.07B params</div>
224
- </div>
225
- </div>
226
- <style>@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}</style>
227
- """
228
-
229
- HOW_IT_WORKS = """
230
- <div style='display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin:16px 0 4px;'>
231
- <div style='background:#181b20;border:1px solid #1e2229;border-radius:10px;
232
- padding:12px;text-align:center;font-size:12px;color:#6b7280'>
233
- <div style='font-size:20px;margin-bottom:6px'>πŸ“Έ</div>
234
- <strong style='color:#9ca3af;display:block;margin-bottom:3px'>1. Photograph</strong>
235
- Aim at any crack or stain
236
- </div>
237
- <div style='background:#181b20;border:1px solid #1e2229;border-radius:10px;
238
- padding:12px;text-align:center;font-size:12px;color:#6b7280'>
239
- <div style='font-size:20px;margin-bottom:6px'>πŸ”</div>
240
- <strong style='color:#9ca3af;display:block;margin-bottom:3px'>2. Analyse</strong>
241
- AI inspects in ~12 sec
242
- </div>
243
- <div style='background:#181b20;border:1px solid #1e2229;border-radius:10px;
244
- padding:12px;text-align:center;font-size:12px;color:#6b7280'>
245
- <div style='font-size:20px;margin-bottom:6px'>β‚Ή</div>
246
- <strong style='color:#9ca3af;display:block;margin-bottom:3px'>3. Get Answer</strong>
247
- Type Β· Severity Β· Cost
248
- </div>
249
- </div>
250
  """
251
 
252
- FOOTER_HTML = """
253
- <div style='text-align:center;padding:28px 0 8px;border-top:1px solid #1e2229;
254
- font-size:11px;color:#374151;font-family:monospace;line-height:2'>
255
- <p style='color:#4b5563'>Qwen2-VL-2B fine-tuned on 8,860 deduplicated defect images Β· No cloud APIs</p>
256
- <p>
257
- <a href='https://huggingface.co/ritvik360/gharscan-qwen2vl-lora' target='_blank' style='color:#4b5563;text-decoration:none'>Model</a>
258
- Β· <a href='https://huggingface.co/datasets/ritvik360/gharscan-defect-dataset' target='_blank' style='color:#4b5563;text-decoration:none'>Dataset</a>
259
- Β· <a href='https://huggingface.co/ritvik360/gharscan-qwen2vl-gguf' target='_blank' style='color:#4b5563;text-decoration:none'>GGUF</a>
260
- Β· <a href='https://huggingface.co/blog/ritvik360/gharscan' target='_blank' style='color:#4b5563;text-decoration:none'>Blog</a>
261
- </p>
262
- <p style='color:#1f2937;margin-top:4px'>
263
- πŸ—οΈ Build Small Hackathon 2026 Β· Backyard AI Track
264
- </p>
265
- <p style='color:#1f2937'>GharScan is a triage aid, not a substitute for professional structural assessment.</p>
266
- </div>
267
- """
268
-
269
- # ── Build UI ───────────────────────────────────────────────────────────────────
270
  with gr.Blocks(
271
  css=CSS,
272
- title="GharScan β€” AI Building Defect Inspector",
273
  theme=gr.themes.Base(
274
  primary_hue="blue",
275
  neutral_hue="slate",
276
- font=gr.themes.GoogleFont("Inter"),
277
  )
278
  ) as demo:
279
-
280
- gr.HTML(HEADER)
281
- gr.HTML(HOW_IT_WORKS)
282
-
283
- image_input = gr.Image(
284
- sources=["upload", "webcam"],
285
- type="pil",
286
- label="πŸ“· DROP IMAGE Β· CLICK TO UPLOAD Β· USE WEBCAM",
287
- height=300,
288
- elem_classes=["image-upload-zone"],
289
- show_download_button=False,
290
- show_share_button=False,
291
- )
292
-
293
- analyse_btn = gr.Button(
294
- "πŸ” Analyse Defect",
295
- variant="primary",
296
- size="lg",
 
 
 
 
 
 
 
 
 
 
 
297
  )
298
 
299
- gr.HTML("<div style='height:4px'></div>")
300
-
301
- # Example images
302
- gr.Examples(
303
- examples=[
304
- ["https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Cracked_Wall.jpg/640px-Cracked_Wall.jpg"],
305
- ],
306
- inputs=image_input,
307
- label="β–Έ Try an example",
308
- examples_per_page=3,
309
- )
310
 
311
- report_output = gr.HTML(
312
- value="",
313
- elem_classes=["output-html"],
 
 
314
  )
315
 
316
- gr.HTML(FOOTER_HTML)
317
-
318
- analyse_btn.click(
319
- fn=analyze_image,
320
- inputs=[image_input],
321
- outputs=[report_output],
322
- api_name="analyze",
323
- )
 
324
 
325
- demo.queue(max_size=10)
326
  demo.launch()
 
1
  """
2
+ app.py β€” GharScan HuggingFace Space
3
+ Uses gr.Blocks (ZeroGPU-compatible). Custom CSS for Off-Brand badge.
4
  """
5
+ import os
6
  import spaces
7
  import gradio as gr
8
  from PIL import Image
9
+ from pathlib import Path
10
+
11
  from inference import run_gharscan_pipeline
12
  from agent_trace import AgentTraceLogger
13
 
14
  trace_logger = AgentTraceLogger()
15
 
16
+ # ── ZeroGPU-compatible inference ──────────────────────────────────────────────
17
  @spaces.GPU
18
+ def analyze_image(image: Image.Image, language: str) -> dict:
19
  if image is None:
20
+ return {}
21
  session = trace_logger.start_trace()
22
+ report = run_gharscan_pipeline(image, language=language, trace_session=session)
23
  trace_logger.save_trace(session)
24
+ return report
 
25
 
26
+ def analyze_and_render(image, language):
27
+ if image is None:
28
+ return "<p style='color:#6b7280;padding:20px'>Please upload or take a photo first.</p>"
29
+ r = analyze_image(image, language)
30
  if not r.get("analysis_ok"):
31
+ return f"<p style='color:#ef4444;padding:20px'>⚠️ {r.get('description','Analysis failed.')}</p>"
32
+
33
+ color = r.get("severity_color", "#6b7280")
34
+ sev = r.get("severity", 0)
35
+ pct = sev * 20
36
+
37
+ struct_html = ""
38
+ if r.get("is_structural"):
39
+ struct_html = f"""<div style='background:rgba(127,29,29,0.25);border:1px solid #7f1d1d;border-radius:8px;padding:12px;margin-bottom:12px;color:#fca5a5'>
40
+ ⚠️ <strong>STRUCTURAL RISK</strong> β€” {r.get("structural_reasoning","")}
41
  </div>"""
42
+ else:
43
+ struct_html = "<div style='background:rgba(21,128,61,0.2);border:1px solid #166534;border-radius:8px;padding:10px;margin-bottom:12px;color:#86efac'>βœ… Not Structural β€” No immediate safety risk</div>"
44
 
45
+ liability = ""
46
+ if r.get("show_liability_banner"):
47
+ liability = f"<div style='background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);border-radius:8px;padding:12px;color:#fca5a5;font-size:12px;margin-top:10px'>⚠️ {r.get('liability_text','')}</div>"
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ disclaimer = ""
50
+ if r.get("disclaimer"):
51
+ disclaimer = f"<div style='border-left:3px solid #f59e0b;padding:10px 14px;font-size:12px;color:#9ca3af;margin-top:8px'>{r['disclaimer']}</div>"
 
52
 
53
+ monsoon = ""
54
+ if r.get("monsoon_risk"):
55
+ monsoon = "<div style='background:rgba(234,179,8,0.1);border:1px solid rgba(234,179,8,0.3);border-radius:6px;padding:10px;color:#fde68a;font-size:13px;margin-top:10px'>🌧️ <strong>Monsoon Risk:</strong> This defect worsens during heavy rainfall. Address before June.</div>"
 
 
 
 
 
 
 
56
 
57
  return f"""
58
+ <div style='background:#181b20;border:1px solid #2a2f38;border-radius:14px;padding:20px;font-family:Inter,sans-serif;color:#e8eaed;max-width:600px'>
59
+ {struct_html}
60
+ <div style='display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:14px'>
61
+ <div>
62
+ <div style='font-size:18px;font-weight:700'>{r.get("defect_display","")}</div>
63
+ <div style='font-size:11px;color:#8b9099;margin-top:3px'>{r.get("defect_type","").replace("_"," ").upper()}</div>
64
+ </div>
65
+ <div style='background:#20242b;border:2px solid {color};border-radius:10px;padding:8px 14px;text-align:center'>
66
+ <div style='font-size:22px;font-weight:700;color:{color}'>{sev}</div>
67
+ <div style='font-size:10px;color:#8b9099'>{r.get("severity_label","").upper()}</div>
68
+ </div>
69
+ </div>
70
+ <div style='background:#20242b;border-radius:4px;height:8px;margin-bottom:4px'>
71
+ <div style='width:{pct}%;height:8px;border-radius:4px;background:{color}'></div>
72
+ </div>
73
+ <div style='display:flex;justify-content:space-between;font-size:10px;color:#555d6b;margin-bottom:14px'>
74
+ <span>Cosmetic</span><span>Moderate</span><span>Critical</span>
75
+ </div>
76
+ <div style='height:1px;background:#1e2229;margin:12px 0'></div>
77
+ <div style='margin-bottom:10px'>
78
+ <div style='font-size:10px;color:#6fb3e0;font-family:monospace;letter-spacing:.06em'>WHAT IT IS</div>
79
+ <div style='font-size:14px;margin-top:4px'>{r.get("description","")}</div>
80
+ </div>
81
+ <div style='margin-bottom:10px'>
82
+ <div style='font-size:10px;color:#6fb3e0;font-family:monospace;letter-spacing:.06em'>WHY IT HAPPENS</div>
83
+ <div style='font-size:14px;margin-top:4px'>{r.get("primary_cause","")}</div>
84
+ </div>
85
+ <div style='height:1px;background:#1e2229;margin:12px 0'></div>
86
+ <div style='background:rgba(59,130,246,0.08);border:1px solid rgba(59,130,246,0.2);border-radius:10px;padding:12px;margin-bottom:10px'>
87
+ <div style='font-size:10px;color:#6fb3e0;font-family:monospace'>WHAT TO DO</div>
88
+ <div style='font-size:14px;font-weight:500;color:#93c5fd;margin-top:4px'>{r.get("immediate_action","")}</div>
89
+ </div>
90
+ <div style='margin-bottom:12px'>
91
+ <div style='font-size:10px;color:#6fb3e0;font-family:monospace'>WHEN TO ACT</div>
92
+ <div style='font-size:14px;font-weight:500;margin-top:4px'>{r.get("urgency_display","")}</div>
93
+ </div>
94
+ <div style='height:1px;background:#1e2229;margin:12px 0'></div>
95
+ <div style='background:#20242b;border-radius:10px;padding:14px'>
96
+ <div style='font-size:22px;font-weight:700;color:#22c55e'>{r.get("cost_range_inr","")}</div>
97
+ <div style='font-size:13px;color:#8b9099;margin-top:6px'>πŸ‘· {r.get("professional_display","")}</div>
98
+ </div>
99
+ {monsoon}
100
+ {liability}
101
+ {disclaimer}
102
  </div>
103
+ """
104
 
105
+ # ── Custom CSS ─────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  CSS = """
107
+ body, .gradio-container { background: #0f1114 !important; color: #e8eaed !important; }
108
+ .gradio-container { max-width: 700px !important; margin: 0 auto !important; }
109
+ .gr-button-primary { background: linear-gradient(135deg,#2563eb,#1d4ed8) !important; border: none !important; }
110
+ .gr-button-primary:hover { opacity: 0.9 !important; }
111
+ footer { display: none !important; }
112
+ #component-0 { padding: 20px !important; }
113
+ .dark { --background-fill-primary: #181b20; --background-fill-secondary: #20242b; --border-color-primary: #2a2f38; --color-text-body: #e8eaed; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  """
115
 
116
+ # ── UI ──────────────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  with gr.Blocks(
118
  css=CSS,
119
+ title="GharScan β€” Building Defect Inspector",
120
  theme=gr.themes.Base(
121
  primary_hue="blue",
122
  neutral_hue="slate",
 
123
  )
124
  ) as demo:
125
+ gr.HTML("""
126
+ <div style='display:flex;align-items:center;justify-content:space-between;
127
+ padding:14px 0;border-bottom:1px solid #2a2f38;margin-bottom:20px'>
128
+ <div style='display:flex;align-items:center;gap:10px'>
129
+ <span style='font-size:26px'>πŸ—οΈ</span>
130
+ <div>
131
+ <div style='font-size:18px;font-weight:700;color:#e8eaed'>GharScan</div>
132
+ <div style='font-size:11px;color:#8b9099'>AI Building Defect Inspector Β· India</div>
133
+ </div>
134
+ </div>
135
+ <div style='background:#20242b;border:1px solid #2a2f38;border-radius:20px;padding:4px 12px;
136
+ font-size:11px;color:#6fb3e0;font-family:monospace'>
137
+ ● Qwen2-VL-2B Β· 2.07B
138
+ </div>
139
+ </div>
140
+ """)
141
+
142
+ with gr.Row():
143
+ image_input = gr.Image(
144
+ sources=["upload", "webcam"],
145
+ type="pil",
146
+ label="πŸ“Έ Take Photo or Upload",
147
+ height=300,
148
+ )
149
+
150
+ language = gr.Radio(
151
+ choices=["en", "hi"],
152
+ value="en",
153
+ label="Output language",
154
  )
155
 
156
+ analyze_btn = gr.Button("πŸ” Analyse Defect", variant="primary", size="lg")
157
+ report_output = gr.HTML(label="Inspection Report")
 
 
 
 
 
 
 
 
 
158
 
159
+ analyze_btn.click(
160
+ fn=analyze_and_render,
161
+ inputs=[image_input, language],
162
+ outputs=report_output,
163
+ api_name="analyze"
164
  )
165
 
166
+ gr.HTML("""
167
+ <div style='text-align:center;padding:20px 0;font-size:11px;color:#555d6b;
168
+ border-top:1px solid #1e2229;margin-top:20px'>
169
+ <p>Qwen2-VL-2B fine-tuned on Indian building defects Β· No cloud APIs</p>
170
+ <p>πŸ—οΈ Built for <a href="https://huggingface.co/build-small-hackathon"
171
+ style='color:#8b9099'>Build Small Hackathon 2026</a> Β· Backyard AI Track</p>
172
+ <p style='color:#374151'>GharScan is a triage aid, not a substitute for professional structural assessment.</p>
173
+ </div>
174
+ """)
175
 
176
+ demo.queue()
177
  demo.launch()