Update app.py
Browse files
app.py
CHANGED
|
@@ -1,138 +1,14 @@
|
|
| 1 |
"""
|
| 2 |
Rahbar v9.0 β Pakistan AI Civic Complaint Platform
|
| 3 |
-
|
| 4 |
-
β’ Gradio 6+ compatible (css in launch, no type= on Chatbot)
|
| 5 |
-
β’ GPS: IP geolocation β shows city on map automatically
|
| 6 |
-
β’ Map: Plotly Scattermap, click-to-fill street/landmark box
|
| 7 |
-
β’ Full Pakistan coverage (not just big cities β any area)
|
| 8 |
-
β’ PDF via ReportLab (professional, no grid lines)
|
| 9 |
-
β’ Voice input/output fully working in chatbot
|
| 10 |
-
β’ Light + Dark mode CSS (auto + manual toggle)
|
| 11 |
-
β’ All UI in English; report content in selected language
|
| 12 |
"""
|
| 13 |
|
| 14 |
import os, io, re, uuid, base64, datetime, urllib.parse
|
| 15 |
from PIL import Image
|
| 16 |
import gradio as gr
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
complaint_log = []
|
| 21 |
-
|
| 22 |
-
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 23 |
-
# IP GEOLOCATION
|
| 24 |
-
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
-
def get_location_from_ip():
|
| 26 |
-
try:
|
| 27 |
-
import requests
|
| 28 |
-
r = requests.get("https://ipinfo.io/json", timeout=6)
|
| 29 |
-
if r.status_code == 200:
|
| 30 |
-
d = r.json()
|
| 31 |
-
loc = d.get("loc", "")
|
| 32 |
-
if loc and "," in loc:
|
| 33 |
-
lat, lon = map(float, loc.split(","))
|
| 34 |
-
return lat, lon, d.get("city", "Unknown"), d.get("region", "Unknown")
|
| 35 |
-
except:
|
| 36 |
-
pass
|
| 37 |
-
try:
|
| 38 |
-
import requests
|
| 39 |
-
r = requests.get("http://ip-api.com/json/", timeout=6)
|
| 40 |
-
if r.status_code == 200:
|
| 41 |
-
d = r.json()
|
| 42 |
-
if d.get("status") == "success":
|
| 43 |
-
return float(d["lat"]), float(d["lon"]), d.get("city", "Unknown"), d.get("regionName", "Unknown")
|
| 44 |
-
except:
|
| 45 |
-
pass
|
| 46 |
-
return None
|
| 47 |
-
|
| 48 |
-
def reverse_geocode(lat, lon):
|
| 49 |
-
try:
|
| 50 |
-
import requests
|
| 51 |
-
url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}&zoom=17&addressdetails=1"
|
| 52 |
-
r = requests.get(url, headers={"User-Agent": "Rahbar/9.0"}, timeout=6)
|
| 53 |
-
if r.status_code == 200:
|
| 54 |
-
d = r.json()
|
| 55 |
-
a = d.get("address", {})
|
| 56 |
-
parts = []
|
| 57 |
-
for k in ("road", "pedestrian", "footway", "residential"):
|
| 58 |
-
if a.get(k):
|
| 59 |
-
parts.append(a[k])
|
| 60 |
-
break
|
| 61 |
-
for k in ("suburb", "neighbourhood", "quarter", "village", "town"):
|
| 62 |
-
if a.get(k):
|
| 63 |
-
parts.append(a[k])
|
| 64 |
-
break
|
| 65 |
-
for k in ("city", "county", "state_district", "state"):
|
| 66 |
-
if a.get(k):
|
| 67 |
-
parts.append(a[k])
|
| 68 |
-
break
|
| 69 |
-
if parts:
|
| 70 |
-
return ", ".join(p.strip() for p in parts if p.strip())
|
| 71 |
-
except:
|
| 72 |
-
pass
|
| 73 |
-
return f"{lat:.5f}, {lon:.5f}"
|
| 74 |
-
|
| 75 |
-
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 76 |
-
# PLOTLY MAP
|
| 77 |
-
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 78 |
-
PAKISTAN_CENTRE = (30.3753, 69.3451)
|
| 79 |
-
|
| 80 |
-
CITY_COORDS = {
|
| 81 |
-
"Lahore": (31.5204, 74.3587), "Karachi": (24.8607, 67.0011),
|
| 82 |
-
"Islamabad": (33.6844, 73.0479), "Rawalpindi": (33.5651, 73.0169),
|
| 83 |
-
"Faisalabad": (31.4181, 73.0776), "Multan": (30.1575, 71.5249),
|
| 84 |
-
"Peshawar": (34.0151, 71.5249), "Quetta": (30.1798, 66.9750),
|
| 85 |
-
"Gujranwala": (32.1877, 74.1945), "Sialkot": (32.4945, 74.5229),
|
| 86 |
-
"Sukkur": (27.7052, 68.8574), "Hyderabad": (25.3960, 68.3578),
|
| 87 |
-
"Bahawalpur": (29.3956, 71.6836), "Sargodha": (32.0836, 72.6711),
|
| 88 |
-
"Abbottabad": (34.1558, 73.2194), "Gilgit": (35.9221, 74.3085),
|
| 89 |
-
"Gwadar": (25.1216, 62.3254), "Skardu": (35.2971, 75.6360),
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
ALL_CITIES = sorted(CITY_COORDS.keys())
|
| 93 |
-
|
| 94 |
-
def build_map(lat, lon, label="", zoom=13):
|
| 95 |
-
try:
|
| 96 |
-
import plotly.graph_objects as go
|
| 97 |
-
label = label or f"{lat:.4f}, {lon:.4f}"
|
| 98 |
-
fig = go.Figure(go.Scattermap(
|
| 99 |
-
lat=[lat], lon=[lon],
|
| 100 |
-
mode="markers+text",
|
| 101 |
-
marker=dict(size=16, color="#e8410a", symbol="marker"),
|
| 102 |
-
text=[label[:50]],
|
| 103 |
-
textposition="top right",
|
| 104 |
-
hovertemplate=f"<b>{label}</b><br>Lat: {lat:.5f}<br>Lon: {lon:.5f}<extra></extra>"
|
| 105 |
-
))
|
| 106 |
-
fig.update_layout(
|
| 107 |
-
map=dict(style="open-street-map", center=dict(lat=lat, lon=lon), zoom=zoom),
|
| 108 |
-
margin=dict(r=0, t=0, l=0, b=0),
|
| 109 |
-
height=280,
|
| 110 |
-
paper_bgcolor="rgba(0,0,0,0)",
|
| 111 |
-
plot_bgcolor="rgba(0,0,0,0)",
|
| 112 |
-
clickmode="event+select"
|
| 113 |
-
)
|
| 114 |
-
return fig
|
| 115 |
-
except:
|
| 116 |
-
return None
|
| 117 |
-
|
| 118 |
-
def build_map_city(city_name):
|
| 119 |
-
coords = CITY_COORDS.get(city_name)
|
| 120 |
-
if coords:
|
| 121 |
-
return build_map(coords[0], coords[1], city_name, 12)
|
| 122 |
-
return build_map(PAKISTAN_CENTRE[0], PAKISTAN_CENTRE[1], "Pakistan", 5)
|
| 123 |
-
|
| 124 |
-
def gps_detect(city_hint):
|
| 125 |
-
result = get_location_from_ip()
|
| 126 |
-
if result:
|
| 127 |
-
lat, lon, city, region = result
|
| 128 |
-
addr = reverse_geocode(lat, lon)
|
| 129 |
-
status = f"π Location detected: **{city}, {region}** (lat {lat:.4f}, lon {lon:.4f})"
|
| 130 |
-
fig = build_map(lat, lon, addr)
|
| 131 |
-
return fig, status, addr, lat, lon
|
| 132 |
-
else:
|
| 133 |
-
status = "β οΈ Could not detect location automatically. Please select your city/area."
|
| 134 |
-
fig = build_map_city(city_hint)
|
| 135 |
-
return fig, status, "", None, None
|
| 136 |
|
| 137 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 138 |
# KNOWLEDGE BASE
|
|
@@ -141,18 +17,6 @@ ISSUE_TYPES = ["Garbage", "Pot Hole", "Pipe Leakage"]
|
|
| 141 |
LANGUAGES = ["English", "Urdu", "Punjabi", "Sindhi"]
|
| 142 |
LANG_CODES = {"English": "en", "Urdu": "ur", "Punjabi": "ur", "Sindhi": "ur"}
|
| 143 |
|
| 144 |
-
LEGAL_KNOWLEDGE = [
|
| 145 |
-
{"category": "Garbage", "title": "Punjab Waste Management Act 2014",
|
| 146 |
-
"content": "Local government must act within 48 hours. Fine: Rs.500-50,000. Helpline: 1139",
|
| 147 |
-
"hotline": "1139", "response": "48 hours"},
|
| 148 |
-
{"category": "Pot Hole", "title": "National Highways Safety Ordinance 2000",
|
| 149 |
-
"content": "Road repairs within 72 hours. Compensation for vehicle damage. NHA: 051-9032800",
|
| 150 |
-
"hotline": "051-9032800", "response": "72 hours"},
|
| 151 |
-
{"category": "Pipe Leakage", "title": "Punjab Water Act 2019",
|
| 152 |
-
"content": "WASA must repair within 24 hours. Clean water is a fundamental right. WASA: 042-99200300",
|
| 153 |
-
"hotline": "042-99200300", "response": "24 hours"},
|
| 154 |
-
]
|
| 155 |
-
|
| 156 |
LEGAL_INFO = {
|
| 157 |
"Garbage": {
|
| 158 |
"laws": ["Punjab Waste Management Act 2014", "EPA 1997 Section 11"],
|
|
@@ -191,6 +55,168 @@ LOCALIZED = {
|
|
| 191 |
"Sindhi": "ΩΎΨ§Ψ¦ΩΎ ΩΩΪͺΩΨ¬ 24 ΪͺΩΨ§ΪͺΩ ΫΎ Ω
Ψ±Ω
Ψͺ ΨΆΨ±ΩΨ±Ω Ψ’ΩΩ."}
|
| 192 |
}
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 195 |
# IMAGE ANALYSIS
|
| 196 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -207,6 +233,7 @@ def analyze_image(image_pil, issue_type):
|
|
| 207 |
def get_legal_advice(issue, location, severity, language="English"):
|
| 208 |
info = LEGAL_INFO.get(issue, LEGAL_INFO.get("Garbage", {}))
|
| 209 |
rights = "\n".join(f"β’ {r}" for r in info.get("citizen_rights", []))
|
|
|
|
| 210 |
|
| 211 |
return f"""## Your Legal Rights for {issue}
|
| 212 |
|
|
@@ -219,6 +246,9 @@ def get_legal_advice(issue, location, severity, language="English"):
|
|
| 219 |
**Fine/Penalty:** {info.get('fine', 'N/A')}
|
| 220 |
|
| 221 |
**Escalation Path:** {info.get('escalation', 'CM Portal: 0800-02345')}
|
|
|
|
|
|
|
|
|
|
| 222 |
"""
|
| 223 |
|
| 224 |
# βββββββββββοΏ½οΏ½ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -230,10 +260,20 @@ def legal_chatbot(message, history, language):
|
|
| 230 |
if not message or not message.strip():
|
| 231 |
return history, ""
|
| 232 |
|
| 233 |
-
response = "**Rahbar Legal Assistant**
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
|
| 238 |
history.append({"role": "user", "content": message})
|
| 239 |
history.append({"role": "assistant", "content": response})
|
|
@@ -379,13 +419,13 @@ def generate_pdf(cid, ts, name, cnic, phone, city, location, issue_type, languag
|
|
| 379 |
def make_report(image, issue_type, city, location, name, cnic, phone,
|
| 380 |
description, language, enable_tts):
|
| 381 |
if image is None:
|
| 382 |
-
return (None, "Please upload an image.", "", "", None, "", None, None
|
| 383 |
if not location or not location.strip():
|
| 384 |
-
return (None, "Please enter a location.", "", "", None, "", None, None
|
| 385 |
if not name or not name.strip():
|
| 386 |
-
return (None, "Please enter your full name.", "", "", None, "", None, None
|
| 387 |
if not cnic or not cnic.strip():
|
| 388 |
-
return (None, "Please enter your CNIC number.", "", "", None, "", None, None
|
| 389 |
|
| 390 |
cid = f"RB-{uuid.uuid4().hex[:8].upper()}"
|
| 391 |
ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
@@ -460,10 +500,8 @@ Reference: {cid} | {ts}
|
|
| 460 |
report_tts = text_to_speech(report[:800], language) if enable_tts else None
|
| 461 |
advice_tts = text_to_speech(legal_advice[:600], language) if enable_tts else None
|
| 462 |
pdf_path = generate_pdf(cid, ts, name, cnic, phone, city, location, issue_type, language, severity, status, reason, confidence, info, description or "")
|
| 463 |
-
|
| 464 |
-
map_fig = build_map_city(city)
|
| 465 |
|
| 466 |
-
return (annotated_img, report, wa_md, legal_advice, report_tts, cid, advice_tts, pdf_path
|
| 467 |
|
| 468 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 469 |
# HELPER FUNCTIONS
|
|
@@ -471,6 +509,7 @@ Reference: {cid} | {ts}
|
|
| 471 |
def law_info(issue, language):
|
| 472 |
info = LEGAL_INFO.get(issue, LEGAL_INFO.get("Garbage", {}))
|
| 473 |
rights = "\n".join(f"β’ {r}" for r in info.get("citizen_rights", []))
|
|
|
|
| 474 |
return f"""## Legal Reference: {issue}
|
| 475 |
|
| 476 |
**Applicable Laws:**
|
|
@@ -483,6 +522,9 @@ def law_info(issue, language):
|
|
| 483 |
**Helpline:** {info.get('hotline', 'N/A')}
|
| 484 |
**Response Time:** {info.get('response', 'N/A')}
|
| 485 |
**Escalation:** {info.get('escalation', 'CM Portal: 0800-02345')}
|
|
|
|
|
|
|
|
|
|
| 486 |
"""
|
| 487 |
|
| 488 |
def get_admin_stats():
|
|
@@ -597,12 +639,17 @@ HOTLINES_HTML = """
|
|
| 597 |
</div>
|
| 598 |
"""
|
| 599 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 600 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 601 |
# BUILD UI
|
| 602 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 603 |
def build_ui():
|
| 604 |
-
default_map = build_map_city("Lahore")
|
| 605 |
-
|
| 606 |
with gr.Blocks(title="Rahbar | Pakistan Civic Complaint System") as demo:
|
| 607 |
gr.HTML(HEADER_HTML)
|
| 608 |
|
|
@@ -621,13 +668,12 @@ def build_ui():
|
|
| 621 |
|
| 622 |
gr.HTML('<div class="sec-title">Complaint Details</div>')
|
| 623 |
issue_type = gr.Radio(choices=ISSUE_TYPES, label="Issue Type")
|
| 624 |
-
city_dd = gr.Dropdown(choices=
|
|
|
|
|
|
|
|
|
|
| 625 |
|
| 626 |
-
gr.
|
| 627 |
-
gps_btn = gr.Button("π Detect My Location", variant="secondary")
|
| 628 |
-
gps_status = gr.Markdown("_Click the button above to detect your location_")
|
| 629 |
-
location_tb = gr.Textbox(label="Street / Landmark / Area", placeholder="Enter exact location")
|
| 630 |
-
map_plot = gr.Plot(label="Map", value=default_map)
|
| 631 |
|
| 632 |
desc_tb = gr.Textbox(label="Description (optional)", lines=3)
|
| 633 |
language_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Language")
|
|
@@ -649,15 +695,10 @@ def build_ui():
|
|
| 649 |
legal_out = gr.Markdown()
|
| 650 |
advice_tts_out = gr.Audio(label="Legal Advice Audio")
|
| 651 |
|
| 652 |
-
# GPS and map interactions
|
| 653 |
-
gps_btn.click(fn=gps_detect, inputs=[city_dd], outputs=[map_plot, gps_status, location_tb, gr.State(), gr.State()])
|
| 654 |
-
city_dd.change(fn=build_map_city, inputs=[city_dd], outputs=[map_plot])
|
| 655 |
-
location_tb.change(fn=build_map_city, inputs=[city_dd], outputs=[map_plot])
|
| 656 |
-
|
| 657 |
submit_btn.click(
|
| 658 |
fn=make_report,
|
| 659 |
inputs=[image_input, issue_type, city_dd, location_tb, name_tb, cnic_tb, phone_tb, desc_tb, language_dd, tts_cb],
|
| 660 |
-
outputs=[annotated_out, report_out, wa_out, legal_out, report_tts_out, complaint_id_out, advice_tts_out, pdf_out
|
| 661 |
)
|
| 662 |
|
| 663 |
# Tab 2 - Legal Rights
|
|
|
|
| 1 |
"""
|
| 2 |
Rahbar v9.0 β Pakistan AI Civic Complaint Platform
|
| 3 |
+
Full Pakistan Coverage | GPS Location | Interactive Map
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import os, io, re, uuid, base64, datetime, urllib.parse
|
| 7 |
from PIL import Image
|
| 8 |
import gradio as gr
|
| 9 |
|
| 10 |
+
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
|
| 11 |
+
complaint_log = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 14 |
# KNOWLEDGE BASE
|
|
|
|
| 17 |
LANGUAGES = ["English", "Urdu", "Punjabi", "Sindhi"]
|
| 18 |
LANG_CODES = {"English": "en", "Urdu": "ur", "Punjabi": "ur", "Sindhi": "ur"}
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
LEGAL_INFO = {
|
| 21 |
"Garbage": {
|
| 22 |
"laws": ["Punjab Waste Management Act 2014", "EPA 1997 Section 11"],
|
|
|
|
| 55 |
"Sindhi": "ΩΎΨ§Ψ¦ΩΎ ΩΩΪͺΩΨ¬ 24 ΪͺΩΨ§ΪͺΩ ΫΎ Ω
Ψ±Ω
Ψͺ ΨΆΨ±ΩΨ±Ω Ψ’ΩΩ."}
|
| 56 |
}
|
| 57 |
|
| 58 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 59 |
+
# HTML with GPS and Interactive Map (Works everywhere in Pakistan)
|
| 60 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 61 |
+
MAP_HTML = """
|
| 62 |
+
<div id="map-container" style="margin-bottom: 10px;">
|
| 63 |
+
<div id="gps-status" style="background: #e8f5e9; padding: 10px 12px; border-radius: 8px; margin-bottom: 8px; font-size: 13px; border-left: 4px solid #2e7d32;">
|
| 64 |
+
π <span id="gps-status-text">Click "Get My Location" or click on map to set address</span>
|
| 65 |
+
</div>
|
| 66 |
+
<div id="map" style="height: 300px; width: 100%; border-radius: 12px; border: 2px solid #c8e6c9;"></div>
|
| 67 |
+
<button id="gps-button" style="margin-top: 8px; width: 100%; padding: 10px; background: #2e7d32; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500;">
|
| 68 |
+
π Get My Current Location (GPS)
|
| 69 |
+
</button>
|
| 70 |
+
</div>
|
| 71 |
+
|
| 72 |
+
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
| 73 |
+
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
| 74 |
+
|
| 75 |
+
<script>
|
| 76 |
+
(function() {
|
| 77 |
+
var map = null;
|
| 78 |
+
var marker = null;
|
| 79 |
+
|
| 80 |
+
// Initialize map centered on Pakistan
|
| 81 |
+
function initMap(lat, lng, zoom) {
|
| 82 |
+
if (map) return;
|
| 83 |
+
map = L.map('map').setView([lat || 30.3753, lng || 69.3451], zoom || 5);
|
| 84 |
+
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
| 85 |
+
attribution: '© <a href="https://www.openstreetmap.org/copyright">OSM</a>',
|
| 86 |
+
subdomains: 'abcd',
|
| 87 |
+
maxZoom: 19
|
| 88 |
+
}).addTo(map);
|
| 89 |
+
|
| 90 |
+
marker = L.marker([lat || 30.3753, lng || 69.3451], { draggable: true }).addTo(map);
|
| 91 |
+
|
| 92 |
+
// Update address when marker is dragged
|
| 93 |
+
marker.on('dragend', function(e) {
|
| 94 |
+
var pos = e.target.getLatLng();
|
| 95 |
+
reverseGeocode(pos.lat, pos.lng);
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
// Update address when map is clicked
|
| 99 |
+
map.on('click', function(e) {
|
| 100 |
+
marker.setLatLng(e.latlng);
|
| 101 |
+
reverseGeocode(e.latlng.lat, e.latlng.lng);
|
| 102 |
+
});
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// Reverse geocode using Nominatim (works for all of Pakistan)
|
| 106 |
+
function reverseGeocode(lat, lng) {
|
| 107 |
+
var statusEl = document.getElementById('gps-status-text');
|
| 108 |
+
statusEl.textContent = 'Getting address...';
|
| 109 |
+
statusEl.style.color = '#f9a825';
|
| 110 |
+
|
| 111 |
+
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1&accept-language=en`)
|
| 112 |
+
.then(r => r.json())
|
| 113 |
+
.then(data => {
|
| 114 |
+
var address = '';
|
| 115 |
+
if (data.address) {
|
| 116 |
+
var parts = [];
|
| 117 |
+
// Get road/street name
|
| 118 |
+
if (data.address.road) parts.push(data.address.road);
|
| 119 |
+
if (data.address.hamlet) parts.push(data.address.hamlet);
|
| 120 |
+
if (data.address.village) parts.push(data.address.village);
|
| 121 |
+
if (data.address.town) parts.push(data.address.town);
|
| 122 |
+
if (data.address.city) parts.push(data.address.city);
|
| 123 |
+
if (data.address.district) parts.push(data.address.district);
|
| 124 |
+
if (data.address.state_district) parts.push(data.address.state_district);
|
| 125 |
+
if (data.address.state) parts.push(data.address.state);
|
| 126 |
+
|
| 127 |
+
if (parts.length === 0 && data.display_name) {
|
| 128 |
+
parts = data.display_name.split(',').slice(0, 4);
|
| 129 |
+
}
|
| 130 |
+
address = parts.join(', ');
|
| 131 |
+
}
|
| 132 |
+
if (!address) address = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
| 133 |
+
|
| 134 |
+
// Find and fill the location textbox
|
| 135 |
+
var locationInput = document.querySelector('#location-input textarea, #location-input input, [data-testid="textbox"]');
|
| 136 |
+
if (locationInput) {
|
| 137 |
+
locationInput.value = address;
|
| 138 |
+
locationInput.dispatchEvent(new Event('input', { bubbles: true }));
|
| 139 |
+
locationInput.dispatchEvent(new Event('change', { bubbles: true }));
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
statusEl.textContent = `β
Location set: ${address.substring(0, 60)}`;
|
| 143 |
+
statusEl.style.color = '#2e7d32';
|
| 144 |
+
})
|
| 145 |
+
.catch(() => {
|
| 146 |
+
var addr = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
| 147 |
+
var locationInput = document.querySelector('#location-input textarea, #location-input input, [data-testid="textbox"]');
|
| 148 |
+
if (locationInput) {
|
| 149 |
+
locationInput.value = addr;
|
| 150 |
+
locationInput.dispatchEvent(new Event('input', { bubbles: true }));
|
| 151 |
+
}
|
| 152 |
+
statusEl.textContent = `β οΈ Approximate: ${addr}`;
|
| 153 |
+
statusEl.style.color = '#f9a825';
|
| 154 |
+
});
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
function getLocation() {
|
| 158 |
+
var statusEl = document.getElementById('gps-status-text');
|
| 159 |
+
statusEl.textContent = 'Requesting GPS access...';
|
| 160 |
+
statusEl.style.color = '#f9a825';
|
| 161 |
+
|
| 162 |
+
if (!navigator.geolocation) {
|
| 163 |
+
statusEl.textContent = 'β GPS not supported by your browser';
|
| 164 |
+
statusEl.style.color = '#d32f2f';
|
| 165 |
+
if (!map) initMap(30.3753, 69.3451, 5);
|
| 166 |
+
return;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
navigator.geolocation.getCurrentPosition(
|
| 170 |
+
function(pos) {
|
| 171 |
+
var lat = pos.coords.latitude;
|
| 172 |
+
var lng = pos.coords.longitude;
|
| 173 |
+
statusEl.textContent = 'π GPS acquired! Getting address...';
|
| 174 |
+
statusEl.style.color = '#2e7d32';
|
| 175 |
+
|
| 176 |
+
if (!map) {
|
| 177 |
+
initMap(lat, lng, 14);
|
| 178 |
+
} else {
|
| 179 |
+
map.setView([lat, lng], 14);
|
| 180 |
+
marker.setLatLng([lat, lng]);
|
| 181 |
+
}
|
| 182 |
+
reverseGeocode(lat, lng);
|
| 183 |
+
},
|
| 184 |
+
function(err) {
|
| 185 |
+
var msg = '';
|
| 186 |
+
if (err.code === 1) msg = 'β Permission denied. Please allow location access.';
|
| 187 |
+
else if (err.code === 2) msg = 'β οΈ Location unavailable. Check GPS/WiFi.';
|
| 188 |
+
else if (err.code === 3) msg = 'β±οΈ GPS timed out. Try again.';
|
| 189 |
+
else msg = 'β GPS error: ' + err.message;
|
| 190 |
+
|
| 191 |
+
statusEl.textContent = msg;
|
| 192 |
+
statusEl.style.color = '#d32f2f';
|
| 193 |
+
if (!map) initMap(30.3753, 69.3451, 5);
|
| 194 |
+
},
|
| 195 |
+
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 0 }
|
| 196 |
+
);
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// Initialize on page load
|
| 200 |
+
if (document.readyState === 'loading') {
|
| 201 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 202 |
+
initMap(30.3753, 69.3451, 5);
|
| 203 |
+
var btn = document.getElementById('gps-button');
|
| 204 |
+
if (btn) btn.onclick = getLocation;
|
| 205 |
+
});
|
| 206 |
+
} else {
|
| 207 |
+
initMap(30.3753, 69.3451, 5);
|
| 208 |
+
var btn = document.getElementById('gps-button');
|
| 209 |
+
if (btn) btn.onclick = getLocation;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// Fallback: retry after 1 second
|
| 213 |
+
setTimeout(function() {
|
| 214 |
+
if (!map) initMap(30.3753, 69.3451, 5);
|
| 215 |
+
}, 1000);
|
| 216 |
+
})();
|
| 217 |
+
</script>
|
| 218 |
+
"""
|
| 219 |
+
|
| 220 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 221 |
# IMAGE ANALYSIS
|
| 222 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 233 |
def get_legal_advice(issue, location, severity, language="English"):
|
| 234 |
info = LEGAL_INFO.get(issue, LEGAL_INFO.get("Garbage", {}))
|
| 235 |
rights = "\n".join(f"β’ {r}" for r in info.get("citizen_rights", []))
|
| 236 |
+
local_msg = LOCALIZED.get(issue, {}).get(language, "")
|
| 237 |
|
| 238 |
return f"""## Your Legal Rights for {issue}
|
| 239 |
|
|
|
|
| 246 |
**Fine/Penalty:** {info.get('fine', 'N/A')}
|
| 247 |
|
| 248 |
**Escalation Path:** {info.get('escalation', 'CM Portal: 0800-02345')}
|
| 249 |
+
|
| 250 |
+
---
|
| 251 |
+
*Notice in {language}:* {local_msg}
|
| 252 |
"""
|
| 253 |
|
| 254 |
# βββββββββββοΏ½οΏ½ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 260 |
if not message or not message.strip():
|
| 261 |
return history, ""
|
| 262 |
|
| 263 |
+
response = """**Rahbar Legal Assistant**
|
| 264 |
+
|
| 265 |
+
I can help you with civic issues in Pakistan:
|
| 266 |
+
|
| 267 |
+
β’ **Garbage Complaints** - Punjab Waste Management Act 2014, Helpline: 1139
|
| 268 |
+
β’ **Road/Pothole Complaints** - NHA helpline: 051-9032800
|
| 269 |
+
β’ **Water/Pipe Leakage** - WASA helpline: 042-99200300
|
| 270 |
+
|
| 271 |
+
Please describe your specific issue for detailed guidance, including:
|
| 272 |
+
- What happened?
|
| 273 |
+
- When did it happen?
|
| 274 |
+
- Which authority have you contacted?
|
| 275 |
+
|
| 276 |
+
I'll provide your legal rights and the exact steps to file a complaint."""
|
| 277 |
|
| 278 |
history.append({"role": "user", "content": message})
|
| 279 |
history.append({"role": "assistant", "content": response})
|
|
|
|
| 419 |
def make_report(image, issue_type, city, location, name, cnic, phone,
|
| 420 |
description, language, enable_tts):
|
| 421 |
if image is None:
|
| 422 |
+
return (None, "Please upload an image.", "", "", None, "", None, None)
|
| 423 |
if not location or not location.strip():
|
| 424 |
+
return (None, "Please enter a location.", "", "", None, "", None, None)
|
| 425 |
if not name or not name.strip():
|
| 426 |
+
return (None, "Please enter your full name.", "", "", None, "", None, None)
|
| 427 |
if not cnic or not cnic.strip():
|
| 428 |
+
return (None, "Please enter your CNIC number.", "", "", None, "", None, None)
|
| 429 |
|
| 430 |
cid = f"RB-{uuid.uuid4().hex[:8].upper()}"
|
| 431 |
ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
| 500 |
report_tts = text_to_speech(report[:800], language) if enable_tts else None
|
| 501 |
advice_tts = text_to_speech(legal_advice[:600], language) if enable_tts else None
|
| 502 |
pdf_path = generate_pdf(cid, ts, name, cnic, phone, city, location, issue_type, language, severity, status, reason, confidence, info, description or "")
|
|
|
|
|
|
|
| 503 |
|
| 504 |
+
return (annotated_img, report, wa_md, legal_advice, report_tts, cid, advice_tts, pdf_path)
|
| 505 |
|
| 506 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 507 |
# HELPER FUNCTIONS
|
|
|
|
| 509 |
def law_info(issue, language):
|
| 510 |
info = LEGAL_INFO.get(issue, LEGAL_INFO.get("Garbage", {}))
|
| 511 |
rights = "\n".join(f"β’ {r}" for r in info.get("citizen_rights", []))
|
| 512 |
+
local = LOCALIZED.get(issue, {}).get(language, "")
|
| 513 |
return f"""## Legal Reference: {issue}
|
| 514 |
|
| 515 |
**Applicable Laws:**
|
|
|
|
| 522 |
**Helpline:** {info.get('hotline', 'N/A')}
|
| 523 |
**Response Time:** {info.get('response', 'N/A')}
|
| 524 |
**Escalation:** {info.get('escalation', 'CM Portal: 0800-02345')}
|
| 525 |
+
|
| 526 |
+
---
|
| 527 |
+
*Notice in {language}:* {local}
|
| 528 |
"""
|
| 529 |
|
| 530 |
def get_admin_stats():
|
|
|
|
| 639 |
</div>
|
| 640 |
"""
|
| 641 |
|
| 642 |
+
CITIES = [
|
| 643 |
+
"Lahore", "Karachi", "Islamabad", "Rawalpindi", "Faisalabad", "Multan",
|
| 644 |
+
"Peshawar", "Quetta", "Gujranwala", "Sialkot", "Sukkur", "Hyderabad",
|
| 645 |
+
"Bahawalpur", "Sargodha", "Abbottabad", "Gilgit", "Skardu", "Gwadar",
|
| 646 |
+
"Mardan", "Dera Ismail Khan", "Muzaffarabad", "Mirpur", "Chitral"
|
| 647 |
+
]
|
| 648 |
+
|
| 649 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 650 |
# BUILD UI
|
| 651 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 652 |
def build_ui():
|
|
|
|
|
|
|
| 653 |
with gr.Blocks(title="Rahbar | Pakistan Civic Complaint System") as demo:
|
| 654 |
gr.HTML(HEADER_HTML)
|
| 655 |
|
|
|
|
| 668 |
|
| 669 |
gr.HTML('<div class="sec-title">Complaint Details</div>')
|
| 670 |
issue_type = gr.Radio(choices=ISSUE_TYPES, label="Issue Type")
|
| 671 |
+
city_dd = gr.Dropdown(choices=sorted(CITIES), value="Lahore", label="City/District", allow_custom_value=True)
|
| 672 |
+
|
| 673 |
+
gr.HTML('<div class="sec-title">Location Map</div>')
|
| 674 |
+
gr.HTML(MAP_HTML)
|
| 675 |
|
| 676 |
+
location_tb = gr.Textbox(label="Street / Landmark / Area", placeholder="Enter exact location", elem_id="location-input")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
|
| 678 |
desc_tb = gr.Textbox(label="Description (optional)", lines=3)
|
| 679 |
language_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Language")
|
|
|
|
| 695 |
legal_out = gr.Markdown()
|
| 696 |
advice_tts_out = gr.Audio(label="Legal Advice Audio")
|
| 697 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 698 |
submit_btn.click(
|
| 699 |
fn=make_report,
|
| 700 |
inputs=[image_input, issue_type, city_dd, location_tb, name_tb, cnic_tb, phone_tb, desc_tb, language_dd, tts_cb],
|
| 701 |
+
outputs=[annotated_out, report_out, wa_out, legal_out, report_tts_out, complaint_id_out, advice_tts_out, pdf_out]
|
| 702 |
)
|
| 703 |
|
| 704 |
# Tab 2 - Legal Rights
|