import gradio as gr from data_layer import WardIndex, resolve from model_client import classify_image, draft_complaint from theme import SariMadiTheme, CUSTOM_CSS ward_index = WardIndex() GET_LOCATION_JS = """ () => { const box = document.getElementById("loc-status-box"); const setMsg = (cls, text) => { if (box) { box.className = "loc-status " + cls; box.textContent = text; } }; setMsg("loc-pending", "Locating you..."); return new Promise((resolve) => { if (!navigator.geolocation) { setMsg("loc-err", "Geolocation not supported. Enter coordinates below."); resolve([null, null]); return; } navigator.geolocation.getCurrentPosition( (pos) => { const lat = Math.round(pos.coords.latitude * 1e6) / 1e6; const lng = Math.round(pos.coords.longitude * 1e6) / 1e6; setMsg("loc-ok", "Location set \u00b7 " + lat.toFixed(4) + ", " + lng.toFixed(4)); resolve([lat, lng]); }, (err) => { setMsg("loc-err", "Location blocked. Enter coordinates below."); resolve([null, null]); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 60000 } ); }); } """ SEVERITY_COLORS = {"low": "#16a34a", "medium": "#ea580c", "high": "#dc2626"} def build_result(image, lat, lng, language): if image is None: return _error("Please add a photo of the issue first.") if lat is None or lng is None: return _error("Please share your location or enter coordinates.") try: lat = float(lat) lng = float(lng) except (TypeError, ValueError): return _error("Those coordinates don't look right.") vision = classify_image(image) civic = vision.get("is_civic_issue", False) category = vision.get("category", "other") if not civic or category == "other": return _error( "This doesn't look like a clear civic issue BBMP handles. Point the " "camera directly at a pothole, damaged or blocked road, broken footpath, " "dead streetlight, garbage, an open or overflowing drain, waterlogging, " "or a stray or dead animal." ) info = resolve(ward_index, lat, lng, vision["category"]) if info is None: return _error( "This location is outside Bengaluru's BBMP wards. Sari Madi can only " "route complaints within Bengaluru city limits. Please use a location " "inside Bengaluru." ) officer = info["officer"] if officer is None: return _error("No officer contact found for this ward.") context = { "category_label": info["category_label"], "severity": vision["severity"], "description": vision["description"], "ward_name": info["ward_name"], "ward_no": info["ward_no"], "zone": info["zone"], "officer_role": officer["role"], "officer_name": officer["name"], } complaint = draft_complaint(context, language=language) sev = vision["severity"] sev_color = SEVERITY_COLORS.get(sev, "#ea580c") seen = vision.get("visible_text", "") seen_html = ( f'

Spotted in photo: "{seen}"

' if seen else "" ) card = f"""
{info['category_label']} {sev}

{vision['description']}

{seen_html}
Ward{info['ward_name']} (No. {info['ward_no']})
Zone{info['zone']}
Officer{officer['name']} ({officer['role']})
Contact{officer['phone']}
""" return (gr.update(value=card, visible=True), gr.update(visible=False, value=""), gr.update(value=complaint, visible=True), gr.update(visible=False)) def _error(msg): html = f'
{msg}
' return (gr.update(visible=False), gr.update(value=html, visible=True), gr.update(visible=False), gr.update(visible=False)) with gr.Blocks(theme=SariMadiTheme(), css=CUSTOM_CSS, title="Sari Madi") as demo: gr.HTML( '
' '

Bengaluru civic reporting

' '

Sari Madi

' '

Snap a civic problem. Get it routed to the right BBMP officer ' 'with a ready-to-file complaint in English or Kannada.

' ) with gr.Group(elem_classes="step-card"): gr.HTML('
1Show the problem
') image_in = gr.Image(type="pil", sources=["upload", "webcam"], height=300, label=None, show_label=False) with gr.Group(elem_classes="step-card"): gr.HTML('
2Mark the spot
') loc_btn = gr.Button("Use my current location", elem_classes="locate-btn", variant="secondary") loc_html = gr.HTML('
') with gr.Accordion("Enter coordinates manually", open=False): with gr.Row(): lat_in = gr.Number(label="Latitude", precision=6) lng_in = gr.Number(label="Longitude", precision=6) with gr.Group(elem_classes="step-card"): gr.HTML('
3Choose language
') language_in = gr.Radio(["English", "Kannada"], value="English", show_label=False, elem_classes="lang-radio") generate_btn = gr.Button("Generate complaint", variant="primary", size="lg", elem_classes="generate-btn") error_out = gr.HTML(visible=False) result_empty = gr.HTML( '
' '
📍
' '

Your complaint will appear here

' '

Add a photo, mark the spot, and tap generate.

' '
', visible=True, ) result_card = gr.HTML(visible=False) complaint_out = gr.Textbox(label="Your complaint, ready to file", lines=9, show_copy_button=True, visible=False) loc_btn.click(fn=None, inputs=None, outputs=[lat_in, lng_in], js=GET_LOCATION_JS) generate_btn.click( fn=build_result, inputs=[image_in, lat_in, lng_in, language_in], outputs=[result_card, error_out, complaint_out, result_empty], ) if __name__ == "__main__": demo.launch()