sari-madi / app.py
Zakeertechie3's picture
Update app.py
99f12d0 verified
Raw
History Blame Contribute Delete
7.09 kB
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'<p class="seen-text">Spotted in photo: "{seen}"</p>' if seen else ""
)
card = f"""
<div class="officer-card">
<div class="oc-head">
<span class="oc-issue">{info['category_label']}</span>
<span class="oc-sev" style="background:{sev_color};">{sev}</span>
</div>
<p class="oc-desc">{vision['description']}</p>
{seen_html}
<div class="oc-divider"></div>
<div class="oc-grid">
<div><span class="oc-label">Ward</span><span class="oc-value">{info['ward_name']} (No. {info['ward_no']})</span></div>
<div><span class="oc-label">Zone</span><span class="oc-value">{info['zone']}</span></div>
<div><span class="oc-label">Officer</span><span class="oc-value">{officer['name']} ({officer['role']})</span></div>
<div><span class="oc-label">Contact</span><span class="oc-value"><a href="tel:{officer['phone']}" class="oc-phone">{officer['phone']}</a></span></div>
</div>
</div>
"""
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'<div class="error-banner">{msg}</div>'
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(
'<div id="sari-header">'
'<p class="kicker">Bengaluru civic reporting</p>'
'<h1>Sari Madi</h1>'
'<p>Snap a civic problem. Get it routed to the right BBMP officer '
'with a ready-to-file complaint in English or Kannada.</p></div>'
)
with gr.Group(elem_classes="step-card"):
gr.HTML('<div class="step-head"><span class="step-num">1</span>Show the problem</div>')
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('<div class="step-head"><span class="step-num">2</span>Mark the spot</div>')
loc_btn = gr.Button("Use my current location",
elem_classes="locate-btn", variant="secondary")
loc_html = gr.HTML('<div id="loc-status-box" class="loc-status"></div>')
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('<div class="step-head"><span class="step-num">3</span>Choose language</div>')
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(
'<div class="result-empty">'
'<div class="re-icon">📍</div>'
'<p class="re-title">Your complaint will appear here</p>'
'<p class="re-sub">Add a photo, mark the spot, and tap generate.</p>'
'</div>',
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()