Update app.py
Browse files
app.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
| 1 |
"""
|
| 2 |
╔══════════════════════════════════════════════════════════════╗
|
| 3 |
║ Rahbar | رہبر ║
|
| 4 |
-
║ Pakistan's AI-Powered Civic Complaint System
|
| 5 |
╚══════════════════════════════════════════════════════════════╝
|
| 6 |
-
Gradio 6 compatible
|
| 7 |
-
Fixes
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
"""
|
| 10 |
|
| 11 |
import os, io, re, uuid, base64, datetime, urllib.parse
|
|
@@ -195,12 +200,12 @@ Always include GPS coordinates and photos with your complaint.""",
|
|
| 195 |
]
|
| 196 |
|
| 197 |
|
| 198 |
-
# ─── RAG ENGINE
|
| 199 |
class RAGEngine:
|
| 200 |
def __init__(self):
|
| 201 |
-
self.documents
|
| 202 |
-
self.vectorizer
|
| 203 |
-
self.doc_matrix
|
| 204 |
self._initialized = False
|
| 205 |
|
| 206 |
def initialize(self):
|
|
@@ -218,7 +223,7 @@ class RAGEngine:
|
|
| 218 |
analyzer='char_wb', ngram_range=(2, 5),
|
| 219 |
max_features=8000, sublinear_tf=True, min_df=1,
|
| 220 |
)
|
| 221 |
-
self.doc_matrix
|
| 222 |
self._initialized = True
|
| 223 |
return True
|
| 224 |
except Exception as e:
|
|
@@ -248,9 +253,9 @@ class RAGEngine:
|
|
| 248 |
def _keyword_fallback(self, query, top_k=3):
|
| 249 |
q = query.lower()
|
| 250 |
keywords = {
|
| 251 |
-
"Garbage": ["garbage", "waste", "sanitation", "trash", "1139"],
|
| 252 |
-
"Pot Hole": ["pothole", "pot hole", "road", "nha"],
|
| 253 |
-
"Pipe Leakage": ["water", "wasa", "pipe", "leakage", "contaminated"],
|
| 254 |
}
|
| 255 |
found_cat = None
|
| 256 |
for cat, kws in keywords.items():
|
|
@@ -357,28 +362,33 @@ LEGAL_KB = {
|
|
| 357 |
LOCALIZED = {
|
| 358 |
"Garbage": {
|
| 359 |
"English": "Dumping garbage is a criminal offence under Punjab Waste Management Act 2014. Fine: Rs. 500-50,000. Helpline: 1139",
|
| 360 |
-
"اردو (Urdu)": "کچرا پھینکنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 کے تحت جرم ہے۔ جرمانہ: 500 سے 50,000 روپے۔",
|
| 361 |
-
"پنجابی (Punjabi)": "کچرا سُٹنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 دے تحت جرم اے۔",
|
| 362 |
-
"سندھی (Sindhi)": "ڪچرو اڇلائڻ پنجاب ويسٽ مئنيجمينٽ ايڪٽ 2014 تحت جرم آهي.",
|
| 363 |
},
|
| 364 |
"Pot Hole": {
|
| 365 |
"English": "Road repair is the government's legal obligation within 72 hours under Punjab LGA 2022. NHA: 051-9032800",
|
| 366 |
-
"اردو (Urdu)": "سڑک کی مرمت پنجاب مقامی حکومت ایکٹ 2022 کے تحت 72 گھنٹوں میں حکومت کی ذمہ داری ہے۔",
|
| 367 |
-
"پنجابی (Punjabi)": "سڑک دی مرمت پنجاب LGA 2022 دے تحت 72 گھنٹیاں وچ سرکار دی ذمہ واری اے۔",
|
| 368 |
-
"سندھی (Sindhi)": "سڙڪ جي مرمت پنجاب LGA 2022 تحت 72 ڪلاڪن ۾ حڪومت جي ذميواري آهي.",
|
| 369 |
},
|
| 370 |
"Pipe Leakage": {
|
| 371 |
"English": "Pipe leakage must be repaired within 24 hours by WASA under Punjab Water Act 2019. WASA: 042-99200300",
|
| 372 |
-
"اردو (Urdu)": "پائپ لیکیج کی مرمت پنجاب واٹر ایکٹ 2019 کے تحت WASA کی 24 گھنٹوں میں ذمہ داری ہے۔",
|
| 373 |
-
"پنجابی (Punjabi)": "پائپ لیکیج دی مرمت پنجاب واٹر ایکٹ 2019 دے تحت WASA دی 24 گھنٹیاں وچ ذمہ واری اے۔",
|
| 374 |
-
"سندھی (Sindhi)": "پائپ ليڪيج جي مرمت پنجاب واٽر ايڪٽ 2019 تحت WASA جي 24 ڪلاڪن ۾ ذميواري آهي.",
|
| 375 |
},
|
| 376 |
}
|
| 377 |
|
| 378 |
WASTE_CLASS_IDS = {24,25,26,27,28,32,33,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54}
|
|
|
|
|
|
|
|
|
|
| 379 |
LANG_CODES = {
|
| 380 |
-
"English":
|
| 381 |
-
"
|
|
|
|
|
|
|
| 382 |
}
|
| 383 |
|
| 384 |
|
|
@@ -451,9 +461,9 @@ def parse_gemini_response(text):
|
|
| 451 |
m = re.search(pat, text, re.IGNORECASE)
|
| 452 |
if m:
|
| 453 |
val = m.group(1)
|
| 454 |
-
if key == "status":
|
| 455 |
elif key == "severity": r[key] = int(val)
|
| 456 |
-
else:
|
| 457 |
for pat, key in [
|
| 458 |
(r"REASON:\s*(.+?)(?=REASON_URDU:|SEVERITY:|$)", "reason"),
|
| 459 |
(r"REASON_URDU:\s*(.+?)(?=SEVERITY:|$)", "reason_urdu"),
|
|
@@ -469,9 +479,9 @@ def analyze_with_llama(issue, location, city, yolo_summary, severity, language="
|
|
| 469 |
clean = issue.split(" ", 1)[-1]
|
| 470 |
kb = LEGAL_KB.get(clean, {})
|
| 471 |
lang_map = {
|
| 472 |
-
"اردو (Urdu)": "Respond entirely in Urdu.",
|
| 473 |
-
"پنجابی (Punjabi)": "Respond in Punjabi
|
| 474 |
-
"سندھی (Sindhi)": "Respond in Sindhi.",
|
| 475 |
}
|
| 476 |
lang_instruction = lang_map.get(language, "Respond in clear professional English.")
|
| 477 |
if not GROQ_API_KEY:
|
|
@@ -520,9 +530,9 @@ def legal_chatbot_rag(user_message, history, language):
|
|
| 520 |
retrieved_docs = rag_engine.retrieve(user_message, top_k=3)
|
| 521 |
rag_context = rag_engine.format_context(retrieved_docs)
|
| 522 |
lang_map = {
|
| 523 |
-
"اردو (Urdu)": "Respond entirely in Urdu.",
|
| 524 |
-
"پنجابی (Punjabi)": "Respond in Punjabi
|
| 525 |
-
"سندھی (Sindhi)": "Respond in Sindhi.",
|
| 526 |
}
|
| 527 |
lang_instruction = lang_map.get(language, "Respond in clear professional English.")
|
| 528 |
system_content = f"""You are Rahbar Legal Assistant — a civic rights advisor for Pakistani citizens.
|
|
@@ -561,7 +571,7 @@ RAG Context (from Knowledge Base):
|
|
| 561 |
answer = resp.choices[0].message.content.strip()
|
| 562 |
if retrieved_docs:
|
| 563 |
refs = [f"[{d['title'][:35]}...]" for d in retrieved_docs[:2]]
|
| 564 |
-
answer += f"\n\
|
| 565 |
except Exception as e:
|
| 566 |
answer = f"Error: {e}"
|
| 567 |
return history + [
|
|
@@ -570,7 +580,28 @@ RAG Context (from Knowledge Base):
|
|
| 570 |
], ""
|
| 571 |
|
| 572 |
|
| 573 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 574 |
def make_tts(text, language):
|
| 575 |
try:
|
| 576 |
from gtts import gTTS
|
|
@@ -580,14 +611,23 @@ def make_tts(text, language):
|
|
| 580 |
tts.save(path)
|
| 581 |
return path
|
| 582 |
except Exception as e:
|
| 583 |
-
print(f"TTS error: {e}")
|
| 584 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
|
| 586 |
|
| 587 |
# ─── STT — Groq Whisper primary, SpeechRecognition fallback ──
|
| 588 |
def stt(audio_file):
|
| 589 |
if audio_file is None:
|
| 590 |
-
return "
|
| 591 |
|
| 592 |
def ensure_wav(path):
|
| 593 |
if path.lower().endswith(".wav"):
|
|
@@ -610,11 +650,11 @@ def stt(audio_file):
|
|
| 610 |
model="whisper-large-v3", file=f, response_format="text"
|
| 611 |
)
|
| 612 |
text = result if isinstance(result, str) else result.text
|
| 613 |
-
return text.strip() or "
|
| 614 |
except Exception as e:
|
| 615 |
groq_err = str(e)
|
| 616 |
else:
|
| 617 |
-
groq_err = "GROQ_API_KEY
|
| 618 |
|
| 619 |
try:
|
| 620 |
import speech_recognition as sr
|
|
@@ -625,10 +665,10 @@ def stt(audio_file):
|
|
| 625 |
audio_data = recognizer.record(src)
|
| 626 |
return recognizer.recognize_google(audio_data)
|
| 627 |
except Exception as e2:
|
| 628 |
-
return (f"
|
| 629 |
-
f"Groq Whisper
|
| 630 |
-
f"Fallback
|
| 631 |
-
f"
|
| 632 |
|
| 633 |
|
| 634 |
# ─── LAW REFERENCE ────────────────────────────────────────────
|
|
@@ -637,18 +677,18 @@ def law_info(issue, language):
|
|
| 637 |
kb = LEGAL_KB.get(clean, {})
|
| 638 |
local = LOCALIZED.get(clean, {}).get(language, "")
|
| 639 |
rights = "\n".join(f" * {r}" for r in kb.get("citizen_rights", []))
|
| 640 |
-
out = f"##
|
| 641 |
for law in kb.get("laws", []):
|
| 642 |
out += f" * {law}\n"
|
| 643 |
-
out += (f"\n###
|
| 644 |
-
f"\n###
|
| 645 |
-
f"\n###
|
| 646 |
-
f"\n###
|
| 647 |
-
f"\n###
|
| 648 |
-
f"\n###
|
| 649 |
if local:
|
| 650 |
-
out += f"\n---\n###
|
| 651 |
-
out += f"\n---\n*
|
| 652 |
return out
|
| 653 |
|
| 654 |
|
|
@@ -656,7 +696,7 @@ def law_info(issue, language):
|
|
| 656 |
def get_admin_stats():
|
| 657 |
total = len(complaint_log)
|
| 658 |
if total == 0:
|
| 659 |
-
return "
|
| 660 |
counts = {"Garbage": 0, "Pot Hole": 0, "Pipe Leakage": 0}
|
| 661 |
cities, severities = {}, []
|
| 662 |
for c in complaint_log:
|
|
@@ -667,29 +707,29 @@ def get_admin_stats():
|
|
| 667 |
severities.append(c.get("severity", 5))
|
| 668 |
avg_sev = sum(severities) / len(severities) if severities else 0
|
| 669 |
top_city = max(cities, key=cities.get) if cities else "N/A"
|
| 670 |
-
stats_md = f"""##
|
| 671 |
-
|
|
| 672 |
|--------|-------|
|
| 673 |
-
|
|
| 674 |
-
|
|
| 675 |
-
|
|
| 676 |
|
| 677 |
-
###
|
| 678 |
-
|
|
| 679 |
|-------|-------|
|
| 680 |
-
|
|
| 681 |
-
|
|
| 682 |
-
|
|
| 683 |
|
| 684 |
-
###
|
| 685 |
"""
|
| 686 |
for city, cnt in sorted(cities.items(), key=lambda x: -x[1]):
|
| 687 |
stats_md += f"| {city} | {cnt} |\n"
|
| 688 |
-
log_md = "##
|
| 689 |
for c in reversed(complaint_log[-10:]):
|
| 690 |
log_md += (f"**{c['id']}** | {c['timestamp']} | {c['city']}, {c['location']} | "
|
| 691 |
-
f"{c['issue']} |
|
| 692 |
-
f"
|
| 693 |
return stats_md, log_md
|
| 694 |
|
| 695 |
|
|
@@ -700,7 +740,6 @@ def severity_icon(score):
|
|
| 700 |
return "RED"
|
| 701 |
|
| 702 |
|
| 703 |
-
# ─── Unicode bold for plain-text headings in gr.Textbox ───────
|
| 704 |
def _ubold(text):
|
| 705 |
result = []
|
| 706 |
for ch in str(text):
|
|
@@ -791,13 +830,13 @@ def build_professional_report(complaint_id, timestamp, name, cnic, phone, city,
|
|
| 791 |
def make_report(image, issue_type, city, location, name, cnic, phone,
|
| 792 |
description, language, enable_tts):
|
| 793 |
if image is None:
|
| 794 |
-
return (None, "
|
| 795 |
if not location.strip():
|
| 796 |
-
return (None, "
|
| 797 |
if not name.strip():
|
| 798 |
-
return (None, "
|
| 799 |
if not cnic.strip():
|
| 800 |
-
return (None, "
|
| 801 |
|
| 802 |
complaint_id = f"RB-{uuid.uuid4().hex[:8].upper()}"
|
| 803 |
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
@@ -811,10 +850,10 @@ def make_report(image, issue_type, city, location, name, cnic, phone,
|
|
| 811 |
|
| 812 |
if gemini_status == "REJECTED":
|
| 813 |
return (annotated_img,
|
| 814 |
-
f"
|
| 815 |
-
f"
|
| 816 |
-
f"
|
| 817 |
-
"
|
| 818 |
"", "", None, complaint_id, None)
|
| 819 |
|
| 820 |
if gemini_status == "UNKNOWN" and "GOOGLE_API_KEY not set" in gemini_raw:
|
|
@@ -841,16 +880,16 @@ def make_report(image, issue_type, city, location, name, cnic, phone,
|
|
| 841 |
"name": name, "cnic": cnic, "phone": phone,
|
| 842 |
})
|
| 843 |
|
| 844 |
-
wa_text = (f"
|
| 845 |
-
f"
|
| 846 |
-
f"
|
| 847 |
-
f"
|
| 848 |
-
wa_md = f"[
|
| 849 |
|
| 850 |
report_tts_path = None
|
| 851 |
if enable_tts:
|
| 852 |
-
tts_text = (f"
|
| 853 |
-
f"
|
| 854 |
report_tts_path = make_tts(tts_text, language)
|
| 855 |
|
| 856 |
advice_tts_path = make_tts(llama_advice[:600], language)
|
|
@@ -858,10 +897,39 @@ def make_report(image, issue_type, city, location, name, cnic, phone,
|
|
| 858 |
|
| 859 |
|
| 860 |
def update_areas(city):
|
| 861 |
-
areas = CITIES_AREAS.get(city, ["
|
| 862 |
return gr.Dropdown(choices=areas, value=areas[0])
|
| 863 |
|
| 864 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 865 |
# ─── CSS ──────────────────────────────────────────────────────
|
| 866 |
CSS = """
|
| 867 |
@import url('https://fonts.googleapis.com/css2?family=Noto+Nastaliq+Urdu:wght@400;700&family=Playfair+Display:wght@700;900&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap');
|
|
@@ -923,6 +991,9 @@ label,.gradio-container .label-wrap span{color:var(--text-primary)!important}
|
|
| 923 |
.gradio-container select,.gradio-container [data-testid="dropdown"]{background:var(--surface)!important;color:var(--text-primary)!important;border-color:var(--border-strong)!important}
|
| 924 |
.gradio-container .message.user{background:var(--surface3)!important;color:var(--text-primary)!important}
|
| 925 |
.gradio-container .message.bot{background:var(--surface2)!important;color:var(--text-primary)!important}
|
|
|
|
|
|
|
|
|
|
| 926 |
::-webkit-scrollbar{width:6px;height:6px}
|
| 927 |
::-webkit-scrollbar-track{background:var(--surface2)}
|
| 928 |
::-webkit-scrollbar-thumb{background:var(--g6);border-radius:3px}
|
|
@@ -940,220 +1011,201 @@ HEADER_HTML = """
|
|
| 940 |
<span class="badge badge-ai">YOLOv8</span>
|
| 941 |
<span class="badge badge-ai">Llama 3.3</span>
|
| 942 |
<span class="badge badge-ai">RAG Chatbot</span>
|
| 943 |
-
<span class="badge badge-pk">4
|
| 944 |
<span class="badge badge-live">LIVE</span>
|
| 945 |
</div>
|
| 946 |
"""
|
| 947 |
|
| 948 |
-
# ─── MAP
|
| 949 |
-
#
|
| 950 |
-
#
|
|
|
|
| 951 |
MAP_HTML = """
|
| 952 |
-
<
|
| 953 |
-
<
|
| 954 |
-
|
| 955 |
-
<div style="margin:6px 0 4px">
|
| 956 |
-
<div id="gpsStatusDiv" style="
|
| 957 |
-
font-size:.82rem;min-height:26px;margin-bottom:8px;font-weight:500;
|
| 958 |
-
padding:7px 12px;color:#25a06b;border-radius:8px;
|
| 959 |
-
background:#f4f8f5;border:1px solid #b8d9c5;">
|
| 960 |
-
Map ready. Use the GPS button above or click the map to place a pin.
|
| 961 |
-
</div>
|
| 962 |
-
<div id="rahbarMapDiv" style="
|
| 963 |
-
height:240px;width:100%;border-radius:12px;
|
| 964 |
-
border:2px solid #1f7a52;margin-bottom:6px;
|
| 965 |
-
background:#e8f5e9;position:relative;overflow:hidden;">
|
| 966 |
</div>
|
| 967 |
-
<
|
| 968 |
-
|
|
|
|
| 969 |
</p>
|
| 970 |
</div>
|
| 971 |
|
|
|
|
| 972 |
<script>
|
| 973 |
-
(function(){
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
|
|
|
| 980 |
}
|
|
|
|
| 981 |
|
| 982 |
-
|
| 983 |
-
// Ask parent page to reverse-geocode and fill the textbox
|
| 984 |
-
try { window.parent.postMessage({ type: 'rahbar_coords', lat: lat, lng: lng }, '*'); }
|
| 985 |
-
catch(e) {}
|
| 986 |
-
setStatus('Getting address for: ' + lat.toFixed(5) + ', ' + lng.toFixed(5) + '...', '#f5a623');
|
| 987 |
-
}
|
| 988 |
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
attribution: '© OpenStreetMap', maxZoom: 19
|
| 994 |
-
}).addTo(rbMap);
|
| 995 |
-
rbMarker = L.marker([lat, lng], { draggable: true })
|
| 996 |
-
.addTo(rbMap)
|
| 997 |
-
.bindPopup('Drag pin or click map to adjust')
|
| 998 |
-
.openPopup();
|
| 999 |
-
rbMarker.on('dragend', function(e) {
|
| 1000 |
-
var ll = e.target.getLatLng();
|
| 1001 |
-
notifyParent(ll.lat, ll.lng);
|
| 1002 |
-
});
|
| 1003 |
-
rbMap.on('click', function(e) {
|
| 1004 |
-
rbMarker.setLatLng(e.latlng);
|
| 1005 |
-
notifyParent(e.latlng.lat, e.latlng.lng);
|
| 1006 |
-
});
|
| 1007 |
-
setTimeout(function(){ if (rbMap) rbMap.invalidateSize(); }, 350);
|
| 1008 |
-
}
|
| 1009 |
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
var lat = ev.data.lat, lng = ev.data.lng;
|
| 1015 |
-
if (!rbMap) buildMap(lat, lng);
|
| 1016 |
-
else { rbMarker.setLatLng([lat, lng]); rbMap.setView([lat, lng], 17); }
|
| 1017 |
-
setTimeout(function(){ if (rbMap) rbMap.invalidateSize(); }, 350);
|
| 1018 |
-
}
|
| 1019 |
-
if (ev.data.type === 'rahbar_status') {
|
| 1020 |
-
setStatus(ev.data.msg, ev.data.color || '#25a06b');
|
| 1021 |
-
}
|
| 1022 |
-
});
|
| 1023 |
|
| 1024 |
-
|
| 1025 |
-
|
| 1026 |
-
|
| 1027 |
-
|
| 1028 |
-
setTimeout(tryInit, 200);
|
| 1029 |
-
}
|
| 1030 |
-
}
|
| 1031 |
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
})();
|
| 1037 |
-
</script>
|
| 1038 |
-
"""
|
| 1039 |
|
| 1040 |
-
|
| 1041 |
-
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
GPS_PARENT_SCRIPT = """
|
| 1045 |
-
<script>
|
| 1046 |
-
(function(){
|
| 1047 |
-
// Reverse-geocode coordinates to a human-readable address
|
| 1048 |
-
function reverseGeocode(lat, lng, cb) {
|
| 1049 |
-
var url = 'https://nominatim.openstreetmap.org/reverse?format=jsonv2'
|
| 1050 |
-
+ '&lat=' + lat + '&lon=' + lng + '&zoom=17&addressdetails=1';
|
| 1051 |
-
fetch(url, { headers: { 'Accept-Language': 'en' } })
|
| 1052 |
-
.then(function(r) { return r.json(); })
|
| 1053 |
-
.then(function(d) {
|
| 1054 |
-
var a = d.address || {}, p = [];
|
| 1055 |
-
if (a.road || a.pedestrian) p.push(a.road || a.pedestrian);
|
| 1056 |
-
if (a.suburb || a.neighbourhood) p.push(a.suburb || a.neighbourhood);
|
| 1057 |
-
if (a.city_district || a.county) p.push(a.city_district || a.county);
|
| 1058 |
-
if (!p.length && d.display_name) p.push(d.display_name.split(',').slice(0,3).join(',').trim());
|
| 1059 |
-
cb(p.join(', ') || ('GPS:' + lat.toFixed(5) + ',' + lng.toFixed(5)));
|
| 1060 |
-
})
|
| 1061 |
-
.catch(function() { cb('GPS:' + lat.toFixed(5) + ',' + lng.toFixed(5)); });
|
| 1062 |
-
}
|
| 1063 |
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
|
| 1068 |
-
var el = wrap ? (wrap.querySelector('textarea') || wrap.querySelector('input')) : null;
|
| 1069 |
-
// Fallback: scan all visible inputs for matching placeholder text
|
| 1070 |
-
if (!el) {
|
| 1071 |
-
var all = document.querySelectorAll('textarea, input[type="text"]');
|
| 1072 |
-
for (var i = 0; i < all.length; i++) {
|
| 1073 |
-
var ph = (all[i].placeholder || '').toLowerCase();
|
| 1074 |
-
if (ph.indexOf('gps') > -1 || ph.indexOf('street') > -1 || ph.indexOf('landmark') > -1) {
|
| 1075 |
-
el = all[i]; break;
|
| 1076 |
-
}
|
| 1077 |
-
}
|
| 1078 |
-
}
|
| 1079 |
-
if (!el) { console.warn('Rahbar GPS: location textbox not found'); return; }
|
| 1080 |
-
// Svelte/React-compatible value setter
|
| 1081 |
-
var proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
| 1082 |
-
var setter = Object.getOwnPropertyDescriptor(proto, 'value');
|
| 1083 |
-
if (setter && setter.set) setter.set.call(el, text); else el.value = text;
|
| 1084 |
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
| 1085 |
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
| 1086 |
-
}
|
| 1087 |
|
| 1088 |
-
|
| 1089 |
-
|
| 1090 |
-
|
| 1091 |
-
|
| 1092 |
-
|
| 1093 |
-
|
|
|
|
|
|
|
|
|
|
| 1094 |
|
| 1095 |
-
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
| 1100 |
-
|
| 1101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1102 |
});
|
| 1103 |
-
|
| 1104 |
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1110 |
}
|
| 1111 |
-
msgIframes({ type: 'rahbar_status', msg: 'Requesting GPS — please ALLOW when your browser prompts...', color: '#f5a623' });
|
| 1112 |
-
navigator.geolocation.getCurrentPosition(
|
| 1113 |
-
function(pos) { handleCoords(pos.coords.latitude, pos.coords.longitude); },
|
| 1114 |
-
function(err) {
|
| 1115 |
-
var msgs = {
|
| 1116 |
-
1: '❌ Location access DENIED. Open browser settings, allow this site location access, then retry.',
|
| 1117 |
-
2: '⚠️ Location signal unavailable. Check GPS/WiFi and retry.',
|
| 1118 |
-
3: '⏱️ GPS request timed out. Please try again.'
|
| 1119 |
-
};
|
| 1120 |
-
var msg = msgs[err.code] || ('GPS error: ' + err.message);
|
| 1121 |
-
msgIframes({ type: 'rahbar_status', msg: msg, color: '#e85353' });
|
| 1122 |
-
alert(msg);
|
| 1123 |
-
},
|
| 1124 |
-
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 0 }
|
| 1125 |
-
);
|
| 1126 |
}
|
| 1127 |
-
|
| 1128 |
-
//
|
| 1129 |
-
|
| 1130 |
-
|
| 1131 |
-
|
| 1132 |
-
|
|
|
|
| 1133 |
});
|
|
|
|
| 1134 |
|
| 1135 |
-
|
| 1136 |
-
|
| 1137 |
-
|
| 1138 |
-
|
| 1139 |
-
|
| 1140 |
-
|
| 1141 |
-
|
| 1142 |
-
|
| 1143 |
-
|
| 1144 |
-
|
| 1145 |
-
|
| 1146 |
-
|
| 1147 |
-
|
| 1148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1149 |
}
|
| 1150 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1151 |
|
| 1152 |
-
|
| 1153 |
-
|
| 1154 |
-
|
| 1155 |
-
|
| 1156 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1157 |
</script>
|
| 1158 |
"""
|
| 1159 |
|
|
@@ -1166,97 +1218,87 @@ def build_ui():
|
|
| 1166 |
with gr.Tabs():
|
| 1167 |
|
| 1168 |
# ══════════════════���═══════════════════════════════
|
| 1169 |
-
# TAB 1 —
|
| 1170 |
# ══════════════════════════════════════════════════
|
| 1171 |
-
with gr.Tab("📝
|
| 1172 |
with gr.Row(equal_height=False):
|
| 1173 |
|
| 1174 |
-
# ──
|
| 1175 |
with gr.Column(scale=1, min_width=300):
|
| 1176 |
|
| 1177 |
-
gr.HTML('<div class="card-title">
|
| 1178 |
-
name_tb = gr.Textbox(label="
|
| 1179 |
-
cnic_tb = gr.Textbox(label="
|
| 1180 |
-
phone_tb = gr.Textbox(label="
|
| 1181 |
|
| 1182 |
-
gr.HTML('<div class="card-title" style="margin-top:14px">
|
| 1183 |
-
gr.HTML("""<div class="info-box">
|
| 1184 |
-
|
| 1185 |
-
# sources=["webcam","upload"] enables native camera on mobile/desktop
|
| 1186 |
image_input = gr.Image(
|
| 1187 |
type="pil",
|
| 1188 |
-
label="
|
| 1189 |
sources=["webcam", "upload"],
|
| 1190 |
height=220,
|
| 1191 |
)
|
| 1192 |
|
| 1193 |
-
gr.HTML('<div class="card-title" style="margin-top:14px">
|
| 1194 |
-
issue_type = gr.Radio(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="
|
| 1195 |
-
city_dd = gr.Dropdown(choices=list(CITIES_AREAS.keys()), value="Lahore", label="
|
| 1196 |
-
area_dd = gr.Dropdown(choices=CITIES_AREAS["Lahore"], value="Model Town", label="
|
| 1197 |
|
| 1198 |
-
|
| 1199 |
-
gr.HTML('<div class="card-title" style="margin-top:14px">GPS Location</div>')
|
| 1200 |
gr.HTML("""<div class="warn-box">
|
| 1201 |
-
<strong>
|
| 1202 |
-
|
| 1203 |
-
You can also click the map or drag the pin to set a custom location.
|
| 1204 |
</div>""")
|
| 1205 |
|
| 1206 |
-
#
|
| 1207 |
-
# using addEventListener in the PARENT page (not inside an iframe),
|
| 1208 |
-
# so navigator.geolocation is fully accessible.
|
| 1209 |
gps_btn = gr.Button(
|
| 1210 |
-
"📍
|
| 1211 |
variant="primary",
|
| 1212 |
elem_id="rahbar_gps_btn",
|
| 1213 |
)
|
| 1214 |
|
| 1215 |
-
# Map
|
| 1216 |
gr.HTML(MAP_HTML)
|
| 1217 |
|
| 1218 |
-
# Location textbox — elem_id used by GPS script to fill it
|
| 1219 |
location_tb = gr.Textbox(
|
| 1220 |
-
label="
|
| 1221 |
-
placeholder="GPS
|
| 1222 |
lines=1,
|
| 1223 |
elem_id="rahbar_location_tb",
|
| 1224 |
)
|
| 1225 |
|
| 1226 |
-
|
| 1227 |
-
gr.
|
| 1228 |
-
|
| 1229 |
-
|
| 1230 |
-
language_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Report & Voice Language")
|
| 1231 |
-
tts_cb = gr.Checkbox(label="Read report aloud (Text-to-Speech)", value=False)
|
| 1232 |
-
submit_btn = gr.Button("Submit Complaint", variant="primary", size="lg")
|
| 1233 |
|
| 1234 |
-
# ──
|
| 1235 |
with gr.Column(scale=2, min_width=320):
|
| 1236 |
|
| 1237 |
-
gr.HTML('<div class="card-title">AI
|
| 1238 |
-
annotated_out = gr.Image(label="AI
|
| 1239 |
-
complaint_id_out = gr.Textbox(label="
|
| 1240 |
|
| 1241 |
-
gr.HTML('<div class="card-title" style="margin-top:14px">
|
| 1242 |
report_out = gr.Textbox(
|
| 1243 |
-
label="
|
| 1244 |
lines=22, interactive=False,
|
| 1245 |
-
placeholder="
|
| 1246 |
)
|
| 1247 |
wa_out = gr.Markdown()
|
| 1248 |
-
report_tts_out = gr.Audio(label="
|
| 1249 |
|
| 1250 |
-
gr.HTML('<div class="card-title" style="margin-top:16px">
|
| 1251 |
-
gr.HTML('<div class="info-box">
|
| 1252 |
legal_advice_out = gr.Textbox(
|
| 1253 |
-
label="
|
| 1254 |
lines=12, interactive=False,
|
| 1255 |
-
placeholder="
|
| 1256 |
)
|
| 1257 |
-
advice_tts_out = gr.Audio(label="
|
| 1258 |
|
| 1259 |
-
# ── Event wiring ──────────────────────────────
|
| 1260 |
city_dd.change(fn=update_areas, inputs=[city_dd], outputs=[area_dd])
|
| 1261 |
submit_btn.click(
|
| 1262 |
fn=make_report,
|
|
@@ -1267,43 +1309,78 @@ You can also click the map or drag the pin to set a custom location.
|
|
| 1267 |
)
|
| 1268 |
|
| 1269 |
# ══════════════════════════════════════════════════
|
| 1270 |
-
# TAB 2 —
|
| 1271 |
# ══════════════════════════════════════════════════
|
| 1272 |
-
with gr.Tab("⚖️
|
| 1273 |
|
| 1274 |
-
gr.HTML('<div class="card-title">
|
| 1275 |
with gr.Row():
|
| 1276 |
-
law_issue_dd = gr.Dropdown(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="
|
| 1277 |
-
law_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="
|
| 1278 |
law_out = gr.Markdown()
|
| 1279 |
-
gr.Button("
|
| 1280 |
fn=law_info, inputs=[law_issue_dd, law_lang_dd], outputs=[law_out]
|
| 1281 |
)
|
| 1282 |
gr.HTML("""<div class="info-box">
|
| 1283 |
-
<strong>
|
| 1284 |
-
|
| 1285 |
-
|
| 1286 |
-
WASA
|
| 1287 |
-
CM
|
| 1288 |
-
|
| 1289 |
</div>""")
|
| 1290 |
|
| 1291 |
-
gr.HTML('<div class="card-title" style="margin-top:24px">🤖 RAG
|
| 1292 |
gr.HTML("""<div class="info-box">
|
| 1293 |
-
|
| 1294 |
-
|
| 1295 |
-
|
| 1296 |
</div>""")
|
| 1297 |
-
|
| 1298 |
-
|
|
|
|
|
|
|
|
|
|
| 1299 |
with gr.Row():
|
| 1300 |
chat_input = gr.Textbox(
|
| 1301 |
-
label="
|
| 1302 |
-
placeholder="
|
| 1303 |
lines=2, scale=4
|
| 1304 |
)
|
| 1305 |
-
chat_send_btn = gr.Button("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1306 |
|
|
|
|
| 1307 |
chat_send_btn.click(
|
| 1308 |
fn=legal_chatbot_rag,
|
| 1309 |
inputs=[chat_input, chatbot, chat_lang_dd],
|
|
@@ -1314,75 +1391,83 @@ Ask about water supply, WASA, pipe leakage, garbage, road potholes, or your lega
|
|
| 1314 |
inputs=[chat_input, chatbot, chat_lang_dd],
|
| 1315 |
outputs=[chatbot, chat_input]
|
| 1316 |
)
|
| 1317 |
-
|
| 1318 |
-
|
| 1319 |
-
|
| 1320 |
-
|
| 1321 |
-
|
| 1322 |
-
|
| 1323 |
-
|
| 1324 |
-
|
| 1325 |
-
],
|
| 1326 |
-
|
| 1327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1328 |
)
|
| 1329 |
|
| 1330 |
# ══════════════════════════════════════════════════
|
| 1331 |
-
# TAB 3 —
|
| 1332 |
# ══════════════════════════════════════════════════
|
| 1333 |
-
with gr.Tab("🎤
|
| 1334 |
|
| 1335 |
-
gr.HTML('<div class="card-title">Speech-to-Text
|
| 1336 |
gr.HTML("""<div class="info-box">
|
| 1337 |
-
|
| 1338 |
-
|
| 1339 |
-
|
| 1340 |
-
|
| 1341 |
</div>""")
|
| 1342 |
gr.HTML("""<div class="warn-box">
|
| 1343 |
-
<strong>
|
| 1344 |
-
|
| 1345 |
</div>""")
|
| 1346 |
audio_in = gr.Audio(
|
| 1347 |
type="filepath",
|
| 1348 |
-
label="
|
| 1349 |
sources=["microphone", "upload"],
|
| 1350 |
)
|
| 1351 |
-
stt_btn = gr.Button("
|
| 1352 |
stt_out = gr.Textbox(
|
| 1353 |
-
label="
|
| 1354 |
lines=6, interactive=True,
|
| 1355 |
-
placeholder="
|
| 1356 |
)
|
| 1357 |
stt_btn.click(fn=stt, inputs=[audio_in], outputs=[stt_out])
|
| 1358 |
|
| 1359 |
-
gr.HTML('<div class="card-title" style="margin-top:24px">
|
| 1360 |
-
gr.HTML('<div class="info-box">
|
| 1361 |
with gr.Row():
|
| 1362 |
-
tts_text_in = gr.Textbox(label="
|
| 1363 |
-
tts_lang_in = gr.Dropdown(choices=LANGUAGES, value="English", label="
|
| 1364 |
-
tts_test_btn = gr.Button("▶
|
| 1365 |
-
tts_test_out = gr.Audio(label="
|
| 1366 |
tts_test_btn.click(fn=make_tts, inputs=[tts_text_in, tts_lang_in], outputs=[tts_test_out])
|
| 1367 |
|
| 1368 |
# ══════════════════════════════════════════════════
|
| 1369 |
-
# TAB 4 —
|
| 1370 |
# ══════════════════════════════════════════════════
|
| 1371 |
-
with gr.Tab("📊
|
| 1372 |
|
| 1373 |
-
gr.HTML('<div class="card-title">
|
| 1374 |
-
refresh_btn = gr.Button("
|
| 1375 |
with gr.Row():
|
| 1376 |
stats_out = gr.Markdown()
|
| 1377 |
log_out = gr.Markdown()
|
| 1378 |
refresh_btn.click(fn=get_admin_stats, outputs=[stats_out, log_out])
|
| 1379 |
|
| 1380 |
gr.HTML("""<div class="info-box" style="margin-top:16px">
|
| 1381 |
-
<strong>RAG
|
| 1382 |
Road Issues Detection Dataset | Urban Issues Dataset | Consumer Complaints Dataset<br>
|
| 1383 |
-
<strong>AI
|
| 1384 |
Gemini 2.0 Flash | YOLOv8 | Llama 3.3 70B | Whisper large-v3 (Groq)<br>
|
| 1385 |
-
<strong>
|
| 1386 |
</div>""")
|
| 1387 |
|
| 1388 |
return demo
|
|
@@ -1390,8 +1475,8 @@ Gemini 2.0 Flash | YOLOv8 | Llama 3.3 70B |
|
|
| 1390 |
|
| 1391 |
# ─── LAUNCH ───────────────────────────────────────────────────
|
| 1392 |
if __name__ == "__main__":
|
| 1393 |
-
print("
|
| 1394 |
-
print("RAG Engine:", "
|
| 1395 |
demo = build_ui()
|
| 1396 |
demo.launch(
|
| 1397 |
server_name="0.0.0.0",
|
|
|
|
| 1 |
"""
|
| 2 |
╔══════════════════════════════════════════════════════════════╗
|
| 3 |
║ Rahbar | رہبر ║
|
| 4 |
+
║ Pakistan's AI-Powered Civic Complaint System v5.0 ║
|
| 5 |
╚══════════════════════════════════════════════════════════════╝
|
| 6 |
+
Gradio 4/5/6 compatible
|
| 7 |
+
Fixes v5.0:
|
| 8 |
+
- GPS button fully working (no js= hack, pure Python + JS injection)
|
| 9 |
+
- Map rendered via gr.HTML with working Leaflet (not sandboxed)
|
| 10 |
+
- Sindhi TTS fixed (uses 'ur' fallback since gtts has no 'sd')
|
| 11 |
+
- Chatbot with voice input + TTS output
|
| 12 |
+
- All UI text in Urdu (not Hindi/Punjabi heading labels)
|
| 13 |
+
- Gradio 6 compatible event wiring
|
| 14 |
"""
|
| 15 |
|
| 16 |
import os, io, re, uuid, base64, datetime, urllib.parse
|
|
|
|
| 200 |
]
|
| 201 |
|
| 202 |
|
| 203 |
+
# ─── RAG ENGINE ───────────────────────────────────────────────
|
| 204 |
class RAGEngine:
|
| 205 |
def __init__(self):
|
| 206 |
+
self.documents = RAG_DOCUMENTS
|
| 207 |
+
self.vectorizer = None
|
| 208 |
+
self.doc_matrix = None
|
| 209 |
self._initialized = False
|
| 210 |
|
| 211 |
def initialize(self):
|
|
|
|
| 223 |
analyzer='char_wb', ngram_range=(2, 5),
|
| 224 |
max_features=8000, sublinear_tf=True, min_df=1,
|
| 225 |
)
|
| 226 |
+
self.doc_matrix = self.vectorizer.fit_transform(corpus)
|
| 227 |
self._initialized = True
|
| 228 |
return True
|
| 229 |
except Exception as e:
|
|
|
|
| 253 |
def _keyword_fallback(self, query, top_k=3):
|
| 254 |
q = query.lower()
|
| 255 |
keywords = {
|
| 256 |
+
"Garbage": ["garbage", "waste", "sanitation", "trash", "1139", "کچرا"],
|
| 257 |
+
"Pot Hole": ["pothole", "pot hole", "road", "nha", "سڑک", "گڑھا"],
|
| 258 |
+
"Pipe Leakage": ["water", "wasa", "pipe", "leakage", "contaminated", "پانی", "پائپ"],
|
| 259 |
}
|
| 260 |
found_cat = None
|
| 261 |
for cat, kws in keywords.items():
|
|
|
|
| 362 |
LOCALIZED = {
|
| 363 |
"Garbage": {
|
| 364 |
"English": "Dumping garbage is a criminal offence under Punjab Waste Management Act 2014. Fine: Rs. 500-50,000. Helpline: 1139",
|
| 365 |
+
"اردو (Urdu)": "کچرا پھینکنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 کے تحت جرم ہے۔ جرمانہ: 500 سے 50,000 روپے۔ ہیلپ لائن: 1139",
|
| 366 |
+
"پنجابی (Punjabi)": "کچرا سُٹنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 دے تحت جرم اے۔ جرمانہ 500 توں 50,000 روپے۔ ہیلپ لائن: 1139",
|
| 367 |
+
"سندھی (Sindhi)": "ڪچرو اڇلائڻ پنجاب ويسٽ مئنيجمينٽ ايڪٽ 2014 تحت جرم آهي. جرمانو: 500 کان 50,000 رپيا. مدد لاءِ: 1139",
|
| 368 |
},
|
| 369 |
"Pot Hole": {
|
| 370 |
"English": "Road repair is the government's legal obligation within 72 hours under Punjab LGA 2022. NHA: 051-9032800",
|
| 371 |
+
"اردو (Urdu)": "سڑک کی مرمت پنجاب مقامی حکومت ایکٹ 2022 کے تحت 72 گھنٹوں میں حکومت کی ذمہ داری ہے۔ NHA: 051-9032800",
|
| 372 |
+
"پنجابی (Punjabi)": "سڑک دی مرمت پنجاب LGA 2022 دے تحت 72 گھنٹیاں وچ سرکار دی ذمہ واری اے۔ NHA: 051-9032800",
|
| 373 |
+
"سندھی (Sindhi)": "سڙڪ جي مرمت پنجاب LGA 2022 تحت 72 ڪلاڪن ۾ حڪومت جي ذميواري آهي. NHA: 051-9032800",
|
| 374 |
},
|
| 375 |
"Pipe Leakage": {
|
| 376 |
"English": "Pipe leakage must be repaired within 24 hours by WASA under Punjab Water Act 2019. WASA: 042-99200300",
|
| 377 |
+
"اردو (Urdu)": "پائپ لیکیج کی مرمت پنجاب واٹر ایکٹ 2019 کے تحت WASA کی 24 گھنٹوں میں ذمہ داری ہے۔ WASA: 042-99200300",
|
| 378 |
+
"پنجابی (Punjabi)": "پائپ لیکیج دی مرمت پنجاب واٹر ایکٹ 2019 دے تحت WASA دی 24 گھنٹیاں وچ ذمہ واری اے۔ WASA: 042-99200300",
|
| 379 |
+
"سندھی (Sindhi)": "پائپ ليڪيج جي مرمت پنجاب واٽر ايڪٽ 2019 تحت WASA جي 24 ڪلاڪن ۾ ذميواري آهي. WASA: 042-99200300",
|
| 380 |
},
|
| 381 |
}
|
| 382 |
|
| 383 |
WASTE_CLASS_IDS = {24,25,26,27,28,32,33,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54}
|
| 384 |
+
|
| 385 |
+
# ─── TTS language codes — Fixed Sindhi fallback ───────────────
|
| 386 |
+
# gTTS does not support 'sd' (Sindhi), so we use 'ur' as closest fallback
|
| 387 |
LANG_CODES = {
|
| 388 |
+
"English": "en",
|
| 389 |
+
"اردو (Urdu)": "ur",
|
| 390 |
+
"پنجابی (Punjabi)": "ur", # Shahmukhi is same as Urdu phonetically for gTTS
|
| 391 |
+
"سندھی (Sindhi)": "ur", # gTTS has no Sindhi — Urdu is closest available
|
| 392 |
}
|
| 393 |
|
| 394 |
|
|
|
|
| 461 |
m = re.search(pat, text, re.IGNORECASE)
|
| 462 |
if m:
|
| 463 |
val = m.group(1)
|
| 464 |
+
if key == "status": r[key] = val.upper()
|
| 465 |
elif key == "severity": r[key] = int(val)
|
| 466 |
+
else: r[key] = val
|
| 467 |
for pat, key in [
|
| 468 |
(r"REASON:\s*(.+?)(?=REASON_URDU:|SEVERITY:|$)", "reason"),
|
| 469 |
(r"REASON_URDU:\s*(.+?)(?=SEVERITY:|$)", "reason_urdu"),
|
|
|
|
| 479 |
clean = issue.split(" ", 1)[-1]
|
| 480 |
kb = LEGAL_KB.get(clean, {})
|
| 481 |
lang_map = {
|
| 482 |
+
"اردو (Urdu)": "Respond entirely in Urdu script.",
|
| 483 |
+
"پنجابی (Punjabi)": "Respond in Punjabi Shahmukhi script (same as Urdu script but Punjabi language).",
|
| 484 |
+
"سندھی (Sindhi)": "Respond in Sindhi script.",
|
| 485 |
}
|
| 486 |
lang_instruction = lang_map.get(language, "Respond in clear professional English.")
|
| 487 |
if not GROQ_API_KEY:
|
|
|
|
| 530 |
retrieved_docs = rag_engine.retrieve(user_message, top_k=3)
|
| 531 |
rag_context = rag_engine.format_context(retrieved_docs)
|
| 532 |
lang_map = {
|
| 533 |
+
"اردو (Urdu)": "Respond entirely in Urdu script.",
|
| 534 |
+
"پنجابی (Punjabi)": "Respond in Punjabi Shahmukhi script.",
|
| 535 |
+
"سندھی (Sindhi)": "Respond in Sindhi script.",
|
| 536 |
}
|
| 537 |
lang_instruction = lang_map.get(language, "Respond in clear professional English.")
|
| 538 |
system_content = f"""You are Rahbar Legal Assistant — a civic rights advisor for Pakistani citizens.
|
|
|
|
| 571 |
answer = resp.choices[0].message.content.strip()
|
| 572 |
if retrieved_docs:
|
| 573 |
refs = [f"[{d['title'][:35]}...]" for d in retrieved_docs[:2]]
|
| 574 |
+
answer += f"\n\n_ماخذ: {' | '.join(refs)}_"
|
| 575 |
except Exception as e:
|
| 576 |
answer = f"Error: {e}"
|
| 577 |
return history + [
|
|
|
|
| 580 |
], ""
|
| 581 |
|
| 582 |
|
| 583 |
+
def chatbot_voice_input(audio_file, current_text):
|
| 584 |
+
"""STT for chatbot voice input"""
|
| 585 |
+
if audio_file is None:
|
| 586 |
+
return current_text or ""
|
| 587 |
+
transcribed = stt(audio_file)
|
| 588 |
+
return transcribed
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
def chatbot_tts_output(history, language):
|
| 592 |
+
"""Generate TTS for last chatbot response"""
|
| 593 |
+
if not history:
|
| 594 |
+
return None
|
| 595 |
+
last_msg = history[-1]
|
| 596 |
+
if last_msg.get("role") == "assistant":
|
| 597 |
+
text = last_msg.get("content", "")
|
| 598 |
+
# Strip markdown refs
|
| 599 |
+
text = re.sub(r'_ماخذ:.*?_', '', text, flags=re.DOTALL).strip()
|
| 600 |
+
return make_tts(text[:600], language)
|
| 601 |
+
return None
|
| 602 |
+
|
| 603 |
+
|
| 604 |
+
# ─── TTS — Fixed Sindhi support ───────────────────────────────
|
| 605 |
def make_tts(text, language):
|
| 606 |
try:
|
| 607 |
from gtts import gTTS
|
|
|
|
| 611 |
tts.save(path)
|
| 612 |
return path
|
| 613 |
except Exception as e:
|
| 614 |
+
print(f"TTS error for lang={language}: {e}")
|
| 615 |
+
# Fallback: try English
|
| 616 |
+
try:
|
| 617 |
+
from gtts import gTTS
|
| 618 |
+
tts = gTTS(text=str(text)[:600], lang="en", slow=False)
|
| 619 |
+
path = f"/tmp/tts_fallback_{uuid.uuid4().hex[:8]}.mp3"
|
| 620 |
+
tts.save(path)
|
| 621 |
+
return path
|
| 622 |
+
except Exception as e2:
|
| 623 |
+
print(f"TTS fallback error: {e2}")
|
| 624 |
+
return None
|
| 625 |
|
| 626 |
|
| 627 |
# ─── STT — Groq Whisper primary, SpeechRecognition fallback ──
|
| 628 |
def stt(audio_file):
|
| 629 |
if audio_file is None:
|
| 630 |
+
return "آڈیو نہیں ملی۔ براہ کرم پہلے آڈیو ریکارڈ کریں یا اپلوڈ کریں۔"
|
| 631 |
|
| 632 |
def ensure_wav(path):
|
| 633 |
if path.lower().endswith(".wav"):
|
|
|
|
| 650 |
model="whisper-large-v3", file=f, response_format="text"
|
| 651 |
)
|
| 652 |
text = result if isinstance(result, str) else result.text
|
| 653 |
+
return text.strip() or "آڈیو میں کوئی آواز نہیں ملی۔"
|
| 654 |
except Exception as e:
|
| 655 |
groq_err = str(e)
|
| 656 |
else:
|
| 657 |
+
groq_err = "GROQ_API_KEY سیٹ نہیں ہے"
|
| 658 |
|
| 659 |
try:
|
| 660 |
import speech_recognition as sr
|
|
|
|
| 665 |
audio_data = recognizer.record(src)
|
| 666 |
return recognizer.recognize_google(audio_data)
|
| 667 |
except Exception as e2:
|
| 668 |
+
return (f"ٹرانسکرپشن ناکام۔\n"
|
| 669 |
+
f"Groq Whisper خرابی: {groq_err}\n"
|
| 670 |
+
f"Fallback خرابی: {e2}\n"
|
| 671 |
+
f"تجویز: GROQ_API_KEY سیٹ کریں۔")
|
| 672 |
|
| 673 |
|
| 674 |
# ─── LAW REFERENCE ────────────────────────────────────────────
|
|
|
|
| 677 |
kb = LEGAL_KB.get(clean, {})
|
| 678 |
local = LOCALIZED.get(clean, {}).get(language, "")
|
| 679 |
rights = "\n".join(f" * {r}" for r in kb.get("citizen_rights", []))
|
| 680 |
+
out = f"## قانونی حوالہ: {issue}\n\n### قابل اطلاق قوانین\n"
|
| 681 |
for law in kb.get("laws", []):
|
| 682 |
out += f" * {law}\n"
|
| 683 |
+
out += (f"\n### جرمانہ / سزا\n{kb.get('fine','N/A')}\n"
|
| 684 |
+
f"\n### ذمہ دار ادارہ\n{kb.get('authority','N/A')}\n"
|
| 685 |
+
f"\n### ہیلپ لائن\n**{kb.get('hotline','N/A')}**\n"
|
| 686 |
+
f"\n### لازمی جوابی مدت\n{kb.get('response','N/A')}\n"
|
| 687 |
+
f"\n### شہری کے حقوق\n{rights}\n"
|
| 688 |
+
f"\n### اضافی شکایت کا راستہ\n{kb.get('escalation','N/A')}\n")
|
| 689 |
if local:
|
| 690 |
+
out += f"\n---\n### مقامی پیغام ({language})\n> {local}\n"
|
| 691 |
+
out += f"\n---\n*ماخذ: {kb.get('dataset_ref','Pakistani civic law databases')}*"
|
| 692 |
return out
|
| 693 |
|
| 694 |
|
|
|
|
| 696 |
def get_admin_stats():
|
| 697 |
total = len(complaint_log)
|
| 698 |
if total == 0:
|
| 699 |
+
return "ابھی تک کوئی شکایت درج نہیں ہوئی۔", ""
|
| 700 |
counts = {"Garbage": 0, "Pot Hole": 0, "Pipe Leakage": 0}
|
| 701 |
cities, severities = {}, []
|
| 702 |
for c in complaint_log:
|
|
|
|
| 707 |
severities.append(c.get("severity", 5))
|
| 708 |
avg_sev = sum(severities) / len(severities) if severities else 0
|
| 709 |
top_city = max(cities, key=cities.get) if cities else "N/A"
|
| 710 |
+
stats_md = f"""## ایڈمن ڈیش بورڈ
|
| 711 |
+
| معلومات | تعداد |
|
| 712 |
|--------|-------|
|
| 713 |
+
| کل شکایات | **{total}** |
|
| 714 |
+
| اوسط شدت | **{avg_sev:.1f}/10** |
|
| 715 |
+
| سب سے زیادہ شکایات | **{top_city}** |
|
| 716 |
|
| 717 |
+
### نوع کے مطابق
|
| 718 |
+
| مسئلہ | تعداد |
|
| 719 |
|-------|-------|
|
| 720 |
+
| کچرا | {counts['Garbage']} |
|
| 721 |
+
| سڑک کا گڑھا | {counts['Pot Hole']} |
|
| 722 |
+
| پائپ لیکیج | {counts['Pipe Leakage']} |
|
| 723 |
|
| 724 |
+
### شہر کے مطابق
|
| 725 |
"""
|
| 726 |
for city, cnt in sorted(cities.items(), key=lambda x: -x[1]):
|
| 727 |
stats_md += f"| {city} | {cnt} |\n"
|
| 728 |
+
log_md = "## حالیہ شکایات\n\n"
|
| 729 |
for c in reversed(complaint_log[-10:]):
|
| 730 |
log_md += (f"**{c['id']}** | {c['timestamp']} | {c['city']}, {c['location']} | "
|
| 731 |
+
f"{c['issue']} | شدت {c['severity']}/10 | "
|
| 732 |
+
f"شہری: {c.get('name','N/A')} (CNIC: {c.get('cnic','N/A')})\n\n")
|
| 733 |
return stats_md, log_md
|
| 734 |
|
| 735 |
|
|
|
|
| 740 |
return "RED"
|
| 741 |
|
| 742 |
|
|
|
|
| 743 |
def _ubold(text):
|
| 744 |
result = []
|
| 745 |
for ch in str(text):
|
|
|
|
| 830 |
def make_report(image, issue_type, city, location, name, cnic, phone,
|
| 831 |
description, language, enable_tts):
|
| 832 |
if image is None:
|
| 833 |
+
return (None, "براہ کرم ایک تصویر اپلوڈ کریں۔", "", "", None, "", None)
|
| 834 |
if not location.strip():
|
| 835 |
+
return (None, "براہ کرم مقام درج کریں۔", "", "", None, "", None)
|
| 836 |
if not name.strip():
|
| 837 |
+
return (None, "براہ کرم اپنا پورا نام درج کریں۔", "", "", None, "", None)
|
| 838 |
if not cnic.strip():
|
| 839 |
+
return (None, "براہ کرم اپنا CNIC نمبر درج کریں۔", "", "", None, "", None)
|
| 840 |
|
| 841 |
complaint_id = f"RB-{uuid.uuid4().hex[:8].upper()}"
|
| 842 |
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
| 850 |
|
| 851 |
if gemini_status == "REJECTED":
|
| 852 |
return (annotated_img,
|
| 853 |
+
f"شکایت مسترد — AI تصدیق\n\nوجہ: {gemini_reason}\n"
|
| 854 |
+
f"اعتماد: {gemini_parsed.get('confidence','N/A')}\n\n"
|
| 855 |
+
f"براہ کرم مسئلے کی واضح تصویر اپلوڈ کریں ({issue_type})۔\n"
|
| 856 |
+
"آپ کی شکایت لاگ نہیں ہوئی۔",
|
| 857 |
"", "", None, complaint_id, None)
|
| 858 |
|
| 859 |
if gemini_status == "UNKNOWN" and "GOOGLE_API_KEY not set" in gemini_raw:
|
|
|
|
| 880 |
"name": name, "cnic": cnic, "phone": phone,
|
| 881 |
})
|
| 882 |
|
| 883 |
+
wa_text = (f"رہبر شکایت\nRef: {complaint_id}\nمسئلہ: {issue_type}\n"
|
| 884 |
+
f"مقام: {location}, {city}\nشدت: {final_severity}/10\n"
|
| 885 |
+
f"ادارہ: {kb.get('authority','N/A')}\n"
|
| 886 |
+
f"ہیلپ لائن: {kb.get('hotline','N/A')}\nوقت: {timestamp}")
|
| 887 |
+
wa_md = f"[واٹس ایپ پر شیئر کریں](https://wa.me/?text={urllib.parse.quote(wa_text[:1000])})"
|
| 888 |
|
| 889 |
report_tts_path = None
|
| 890 |
if enable_tts:
|
| 891 |
+
tts_text = (f"شکایت {complaint_id} درج ہوئی۔ مسئلہ: {issue_type} "
|
| 892 |
+
f"مقام: {location}, {city}۔ شدت: {final_severity} میں سے 10۔ {local}")
|
| 893 |
report_tts_path = make_tts(tts_text, language)
|
| 894 |
|
| 895 |
advice_tts_path = make_tts(llama_advice[:600], language)
|
|
|
|
| 897 |
|
| 898 |
|
| 899 |
def update_areas(city):
|
| 900 |
+
areas = CITIES_AREAS.get(city, ["علاقہ درج کریں"])
|
| 901 |
return gr.Dropdown(choices=areas, value=areas[0])
|
| 902 |
|
| 903 |
|
| 904 |
+
# ─── GPS reverse geocode (server-side fallback) ───────────────
|
| 905 |
+
def gps_reverse_geocode(lat_str, lng_str):
|
| 906 |
+
"""Called from GPS JS via gr.Button, fills location textbox"""
|
| 907 |
+
try:
|
| 908 |
+
lat = float(lat_str)
|
| 909 |
+
lng = float(lng_str)
|
| 910 |
+
except Exception:
|
| 911 |
+
return ""
|
| 912 |
+
try:
|
| 913 |
+
import urllib.request, json
|
| 914 |
+
url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lng}&zoom=17&addressdetails=1"
|
| 915 |
+
req = urllib.request.Request(url, headers={"User-Agent": "Rahbar/5.0"})
|
| 916 |
+
with urllib.request.urlopen(req, timeout=5) as resp:
|
| 917 |
+
data = json.loads(resp.read())
|
| 918 |
+
a = data.get("address", {})
|
| 919 |
+
parts = []
|
| 920 |
+
if a.get("road") or a.get("pedestrian"):
|
| 921 |
+
parts.append(a.get("road") or a.get("pedestrian"))
|
| 922 |
+
if a.get("suburb") or a.get("neighbourhood"):
|
| 923 |
+
parts.append(a.get("suburb") or a.get("neighbourhood"))
|
| 924 |
+
if a.get("city_district") or a.get("county"):
|
| 925 |
+
parts.append(a.get("city_district") or a.get("county"))
|
| 926 |
+
if not parts and data.get("display_name"):
|
| 927 |
+
parts = data["display_name"].split(",")[:3]
|
| 928 |
+
return ", ".join(p.strip() for p in parts if p.strip()) or f"GPS:{lat:.5f},{lng:.5f}"
|
| 929 |
+
except Exception:
|
| 930 |
+
return f"GPS:{lat:.5f},{lng:.5f}"
|
| 931 |
+
|
| 932 |
+
|
| 933 |
# ─── CSS ──────────────────────────────────────────────────────
|
| 934 |
CSS = """
|
| 935 |
@import url('https://fonts.googleapis.com/css2?family=Noto+Nastaliq+Urdu:wght@400;700&family=Playfair+Display:wght@700;900&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap');
|
|
|
|
| 991 |
.gradio-container select,.gradio-container [data-testid="dropdown"]{background:var(--surface)!important;color:var(--text-primary)!important;border-color:var(--border-strong)!important}
|
| 992 |
.gradio-container .message.user{background:var(--surface3)!important;color:var(--text-primary)!important}
|
| 993 |
.gradio-container .message.bot{background:var(--surface2)!important;color:var(--text-primary)!important}
|
| 994 |
+
#map_container{border-radius:12px;overflow:hidden;border:2px solid var(--border-strong);margin:8px 0}
|
| 995 |
+
#rahbar_map{height:260px;width:100%;background:#e8f5e9}
|
| 996 |
+
.gps-status{font-size:.82rem;min-height:28px;padding:7px 12px;border-radius:8px;background:var(--surface2);border:1px solid var(--border);color:var(--g5);font-weight:500;margin-bottom:8px}
|
| 997 |
::-webkit-scrollbar{width:6px;height:6px}
|
| 998 |
::-webkit-scrollbar-track{background:var(--surface2)}
|
| 999 |
::-webkit-scrollbar-thumb{background:var(--g6);border-radius:3px}
|
|
|
|
| 1011 |
<span class="badge badge-ai">YOLOv8</span>
|
| 1012 |
<span class="badge badge-ai">Llama 3.3</span>
|
| 1013 |
<span class="badge badge-ai">RAG Chatbot</span>
|
| 1014 |
+
<span class="badge badge-pk">4 زبانیں</span>
|
| 1015 |
<span class="badge badge-live">LIVE</span>
|
| 1016 |
</div>
|
| 1017 |
"""
|
| 1018 |
|
| 1019 |
+
# ─── MAP HTML — Leaflet loaded directly (no iframe sandbox) ───
|
| 1020 |
+
# This is injected into the Gradio page DOM directly.
|
| 1021 |
+
# The GPS button Python handler updates lat_state/lng_state,
|
| 1022 |
+
# and a JS watcher moves the map pin accordingly.
|
| 1023 |
MAP_HTML = """
|
| 1024 |
+
<div id="map_container">
|
| 1025 |
+
<div id="gps_status_bar" class="gps-status">
|
| 1026 |
+
نقشہ تیار ہے۔ GPS بٹن دبائیں یا نقشے پر کلک کریں۔
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1027 |
</div>
|
| 1028 |
+
<div id="rahbar_map"></div>
|
| 1029 |
+
<p style="font-size:.71rem;color:var(--text-muted);margin:4px 0 0;padding:0 4px">
|
| 1030 |
+
نقشے پر کلک کریں یا پن گھسیٹیں — GPS بٹن آپ کا پتہ خود بھرے گا
|
| 1031 |
</p>
|
| 1032 |
</div>
|
| 1033 |
|
| 1034 |
+
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
|
| 1035 |
<script>
|
| 1036 |
+
(function waitForLeaflet(){
|
| 1037 |
+
if(typeof L !== 'undefined'){
|
| 1038 |
+
initRahbarMap();
|
| 1039 |
+
} else {
|
| 1040 |
+
var s = document.createElement('script');
|
| 1041 |
+
s.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
|
| 1042 |
+
s.onload = initRahbarMap;
|
| 1043 |
+
document.head.appendChild(s);
|
| 1044 |
}
|
| 1045 |
+
})();
|
| 1046 |
|
| 1047 |
+
var rahbarMap = null, rahbarMarker = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1048 |
|
| 1049 |
+
function setGpsStatus(msg, color){
|
| 1050 |
+
var el = document.getElementById('gps_status_bar');
|
| 1051 |
+
if(el){ el.textContent = msg; el.style.color = color || '#25a06b'; }
|
| 1052 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1053 |
|
| 1054 |
+
function initRahbarMap(){
|
| 1055 |
+
if(rahbarMap) return;
|
| 1056 |
+
var mapEl = document.getElementById('rahbar_map');
|
| 1057 |
+
if(!mapEl){ setTimeout(initRahbarMap, 300); return; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1058 |
|
| 1059 |
+
rahbarMap = L.map('rahbar_map').setView([31.5204, 74.3587], 13);
|
| 1060 |
+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
|
| 1061 |
+
attribution:'© OpenStreetMap',maxZoom:19
|
| 1062 |
+
}).addTo(rahbarMap);
|
|
|
|
|
|
|
|
|
|
| 1063 |
|
| 1064 |
+
rahbarMarker = L.marker([31.5204, 74.3587],{draggable:true})
|
| 1065 |
+
.addTo(rahbarMap)
|
| 1066 |
+
.bindPopup('پن گھسیٹیں یا نقشے پر کلک کریں')
|
| 1067 |
+
.openPopup();
|
|
|
|
|
|
|
|
|
|
| 1068 |
|
| 1069 |
+
rahbarMarker.on('dragend', function(e){
|
| 1070 |
+
var ll = e.target.getLatLng();
|
| 1071 |
+
updateLocationFromCoords(ll.lat, ll.lng);
|
| 1072 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1073 |
|
| 1074 |
+
rahbarMap.on('click', function(e){
|
| 1075 |
+
rahbarMarker.setLatLng(e.latlng);
|
| 1076 |
+
updateLocationFromCoords(e.latlng.lat, e.latlng.lng);
|
| 1077 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1078 |
|
| 1079 |
+
setTimeout(function(){ if(rahbarMap) rahbarMap.invalidateSize(); }, 500);
|
| 1080 |
+
}
|
| 1081 |
+
|
| 1082 |
+
function moveMapPin(lat, lng){
|
| 1083 |
+
if(!rahbarMap){ setTimeout(function(){ moveMapPin(lat,lng); }, 400); return; }
|
| 1084 |
+
rahbarMarker.setLatLng([lat,lng]);
|
| 1085 |
+
rahbarMap.setView([lat,lng], 17);
|
| 1086 |
+
setTimeout(function(){ rahbarMap.invalidateSize(); }, 300);
|
| 1087 |
+
}
|
| 1088 |
|
| 1089 |
+
function updateLocationFromCoords(lat, lng){
|
| 1090 |
+
setGpsStatus('پتہ حاصل ہو رہا ہے: ' + lat.toFixed(5) + ', ' + lng.toFixed(5) + '...', '#f5a623');
|
| 1091 |
+
fetch('https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat='+lat+'&lon='+lng+'&zoom=17&addressdetails=1',
|
| 1092 |
+
{headers:{'Accept-Language':'ur,en'}})
|
| 1093 |
+
.then(function(r){ return r.json(); })
|
| 1094 |
+
.then(function(d){
|
| 1095 |
+
var a = d.address || {}, p = [];
|
| 1096 |
+
if(a.road || a.pedestrian) p.push(a.road || a.pedestrian);
|
| 1097 |
+
if(a.suburb || a.neighbourhood) p.push(a.suburb || a.neighbourhood);
|
| 1098 |
+
if(a.city_district || a.county) p.push(a.city_district || a.county);
|
| 1099 |
+
if(!p.length && d.display_name) p = d.display_name.split(',').slice(0,3);
|
| 1100 |
+
var addr = p.join(', ').trim() || ('GPS:'+lat.toFixed(5)+','+lng.toFixed(5));
|
| 1101 |
+
fillLocationBox(addr);
|
| 1102 |
+
setGpsStatus('✅ مقام ملا: ' + addr, '#25a06b');
|
| 1103 |
+
})
|
| 1104 |
+
.catch(function(){
|
| 1105 |
+
var addr = 'GPS:'+lat.toFixed(5)+','+lng.toFixed(5);
|
| 1106 |
+
fillLocationBox(addr);
|
| 1107 |
+
setGpsStatus('✅ GPS مقام: ' + addr, '#25a06b');
|
| 1108 |
});
|
| 1109 |
+
}
|
| 1110 |
|
| 1111 |
+
function fillLocationBox(text){
|
| 1112 |
+
// Try multiple selectors for Gradio 4/5/6 compatibility
|
| 1113 |
+
var el = null;
|
| 1114 |
+
var wrap = document.getElementById('rahbar_location_tb');
|
| 1115 |
+
if(wrap) el = wrap.querySelector('textarea') || wrap.querySelector('input');
|
| 1116 |
+
if(!el){
|
| 1117 |
+
var all = document.querySelectorAll('textarea,input[type="text"]');
|
| 1118 |
+
for(var i=0;i<all.length;i++){
|
| 1119 |
+
var ph = (all[i].placeholder||'').toLowerCase();
|
| 1120 |
+
if(ph.indexOf('gps')>-1 || ph.indexOf('address')>-1 || ph.indexOf('street')>-1 || ph.indexOf('landmark')>-1){
|
| 1121 |
+
el = all[i]; break;
|
| 1122 |
+
}
|
| 1123 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1124 |
}
|
| 1125 |
+
if(!el){ console.warn('Rahbar: location box not found'); return; }
|
| 1126 |
+
// React/Svelte compatible value setter
|
| 1127 |
+
var proto = el.tagName==='TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
| 1128 |
+
var setter = Object.getOwnPropertyDescriptor(proto,'value');
|
| 1129 |
+
if(setter && setter.set) setter.set.call(el, text); else el.value = text;
|
| 1130 |
+
['input','change'].forEach(function(ev){
|
| 1131 |
+
el.dispatchEvent(new Event(ev,{bubbles:true}));
|
| 1132 |
});
|
| 1133 |
+
}
|
| 1134 |
|
| 1135 |
+
// GPS function — called directly by the GPS button via onclick
|
| 1136 |
+
window.rahbarGetGPS = function(){
|
| 1137 |
+
if(!navigator.geolocation){
|
| 1138 |
+
setGpsStatus('❌ آپ کا براؤزر GPS کی سہولت نہیں دیتا۔', '#e85353');
|
| 1139 |
+
return;
|
| 1140 |
+
}
|
| 1141 |
+
setGpsStatus('📡 GPS مانگ رہے ہیں — براؤزر میں "Allow" کریں...', '#f5a623');
|
| 1142 |
+
navigator.geolocation.getCurrentPosition(
|
| 1143 |
+
function(pos){
|
| 1144 |
+
var lat = pos.coords.latitude, lng = pos.coords.longitude;
|
| 1145 |
+
moveMapPin(lat, lng);
|
| 1146 |
+
updateLocationFromCoords(lat, lng);
|
| 1147 |
+
},
|
| 1148 |
+
function(err){
|
| 1149 |
+
var msgs = {
|
| 1150 |
+
1:'❌ مقام تک رسائی مسترد — براؤزر سیٹنگ میں اس سائٹ کو مقام کی اجازت دیں۔',
|
| 1151 |
+
2:'⚠️ GPS سگنل دستیاب نہیں — GPS/WiFi چیک کریں اور دوبارہ کوشش کریں۔',
|
| 1152 |
+
3:'⏱️ GPS وقت ختم — دوبارہ کوشش کریں۔'
|
| 1153 |
+
};
|
| 1154 |
+
setGpsStatus(msgs[err.code]||('GPS خرابی: '+err.message), '#e85353');
|
| 1155 |
+
},
|
| 1156 |
+
{enableHighAccuracy:true, timeout:15000, maximumAge:0}
|
| 1157 |
+
);
|
| 1158 |
+
};
|
| 1159 |
+
|
| 1160 |
+
// Attach GPS button — works with Gradio 4/5/6
|
| 1161 |
+
function attachGpsBtn(){
|
| 1162 |
+
// Method 1: elem_id wrapper
|
| 1163 |
+
var wrap = document.getElementById('rahbar_gps_btn');
|
| 1164 |
+
var btn = wrap ? wrap.querySelector('button') : null;
|
| 1165 |
+
// Method 2: scan all buttons
|
| 1166 |
+
if(!btn){
|
| 1167 |
+
var btns = document.querySelectorAll('button');
|
| 1168 |
+
for(var i=0;i<btns.length;i++){
|
| 1169 |
+
if(btns[i].textContent.indexOf('GPS') > -1){
|
| 1170 |
+
btn = btns[i]; break;
|
| 1171 |
+
}
|
| 1172 |
}
|
| 1173 |
}
|
| 1174 |
+
if(btn){
|
| 1175 |
+
// Remove existing listeners and add fresh one
|
| 1176 |
+
var fresh = btn.cloneNode(true);
|
| 1177 |
+
btn.parentNode.replaceChild(fresh, btn);
|
| 1178 |
+
fresh.addEventListener('click', function(e){
|
| 1179 |
+
e.preventDefault();
|
| 1180 |
+
e.stopImmediatePropagation();
|
| 1181 |
+
window.rahbarGetGPS();
|
| 1182 |
+
}, true);
|
| 1183 |
+
fresh.setAttribute('onclick', 'event.preventDefault();event.stopPropagation();window.rahbarGetGPS();return false;');
|
| 1184 |
+
console.log('Rahbar: GPS button attached');
|
| 1185 |
+
} else {
|
| 1186 |
+
setTimeout(attachGpsBtn, 500);
|
| 1187 |
+
}
|
| 1188 |
+
}
|
| 1189 |
|
| 1190 |
+
if(document.readyState === 'loading'){
|
| 1191 |
+
document.addEventListener('DOMContentLoaded', function(){ setTimeout(attachGpsBtn, 1000); });
|
| 1192 |
+
} else {
|
| 1193 |
+
setTimeout(attachGpsBtn, 1000);
|
| 1194 |
+
}
|
| 1195 |
+
// Also re-attach on tab changes / Gradio re-renders
|
| 1196 |
+
var gpsObs = new MutationObserver(function(){
|
| 1197 |
+
if(!document.getElementById('rahbar_gps_btn')) return;
|
| 1198 |
+
var wrap = document.getElementById('rahbar_gps_btn');
|
| 1199 |
+
var btn = wrap ? wrap.querySelector('button') : null;
|
| 1200 |
+
if(btn && !btn._rahbarGpsAttached){
|
| 1201 |
+
btn._rahbarGpsAttached = true;
|
| 1202 |
+
btn.addEventListener('click', function(e){
|
| 1203 |
+
e.preventDefault(); e.stopImmediatePropagation();
|
| 1204 |
+
window.rahbarGetGPS();
|
| 1205 |
+
}, true);
|
| 1206 |
+
}
|
| 1207 |
+
});
|
| 1208 |
+
gpsObs.observe(document.body, {childList:true, subtree:true});
|
| 1209 |
</script>
|
| 1210 |
"""
|
| 1211 |
|
|
|
|
| 1218 |
with gr.Tabs():
|
| 1219 |
|
| 1220 |
# ══════════════════���═══════════════════════════════
|
| 1221 |
+
# TAB 1 — شکایت درج کریں
|
| 1222 |
# ══════════════════════════════════════════════════
|
| 1223 |
+
with gr.Tab("📝 شکایت درج کریں"):
|
| 1224 |
with gr.Row(equal_height=False):
|
| 1225 |
|
| 1226 |
+
# ── بائیں: inputs ──────────────────────────
|
| 1227 |
with gr.Column(scale=1, min_width=300):
|
| 1228 |
|
| 1229 |
+
gr.HTML('<div class="card-title">شہری معلومات</div>')
|
| 1230 |
+
name_tb = gr.Textbox(label="پورا نام", placeholder="مثلاً: علی رضا", lines=1)
|
| 1231 |
+
cnic_tb = gr.Textbox(label="شناختی کارڈ نمبر (بغیر ڈیش)", placeholder="1234567890123", lines=1)
|
| 1232 |
+
phone_tb = gr.Textbox(label="فون نمبر (اختیاری)", placeholder="03xxxxxxxxx", lines=1)
|
| 1233 |
|
| 1234 |
+
gr.HTML('<div class="card-title" style="margin-top:14px">مسئلے کی تصویر</div>')
|
| 1235 |
+
gr.HTML("""<div class="info-box">مسئلے کی واضح تصویر اپلوڈ کریں۔
|
| 1236 |
+
موبائل پر: "ویب کیم" کے ذریعے کیمرہ استعمال کر سکتے ہیں۔</div>""")
|
|
|
|
| 1237 |
image_input = gr.Image(
|
| 1238 |
type="pil",
|
| 1239 |
+
label="تصویر اپلوڈ یا کیپچر کریں",
|
| 1240 |
sources=["webcam", "upload"],
|
| 1241 |
height=220,
|
| 1242 |
)
|
| 1243 |
|
| 1244 |
+
gr.HTML('<div class="card-title" style="margin-top:14px">شکایت کی تفصیل</div>')
|
| 1245 |
+
issue_type = gr.Radio(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="مسئلے کی نوعیت")
|
| 1246 |
+
city_dd = gr.Dropdown(choices=list(CITIES_AREAS.keys()), value="Lahore", label="شہر")
|
| 1247 |
+
area_dd = gr.Dropdown(choices=CITIES_AREAS["Lahore"], value="Model Town", label="علاقہ")
|
| 1248 |
|
| 1249 |
+
gr.HTML('<div class="card-title" style="margin-top:14px">GPS مقام</div>')
|
|
|
|
| 1250 |
gr.HTML("""<div class="warn-box">
|
| 1251 |
+
<strong>"میرا GPS مقام حاصل کریں"</strong> بٹن دبائیں اور مقام کی اجازت دیں۔
|
| 1252 |
+
آپ کا پتہ خود بھر جائے گا۔ نقشے پر کلک کر کے بھی مقام تبدیل کر سکتے ہیں۔
|
|
|
|
| 1253 |
</div>""")
|
| 1254 |
|
| 1255 |
+
# GPS button — elem_id for JS targeting
|
|
|
|
|
|
|
| 1256 |
gps_btn = gr.Button(
|
| 1257 |
+
"📍 میرا GPS مقام حاصل کریں",
|
| 1258 |
variant="primary",
|
| 1259 |
elem_id="rahbar_gps_btn",
|
| 1260 |
)
|
| 1261 |
|
| 1262 |
+
# Map — loaded directly in page (not iframe)
|
| 1263 |
gr.HTML(MAP_HTML)
|
| 1264 |
|
|
|
|
| 1265 |
location_tb = gr.Textbox(
|
| 1266 |
+
label="گلی / نشانی / GPS پتہ",
|
| 1267 |
+
placeholder="GPS پتہ یہاں خود بھرے گا — یا خود لکھیں",
|
| 1268 |
lines=1,
|
| 1269 |
elem_id="rahbar_location_tb",
|
| 1270 |
)
|
| 1271 |
|
| 1272 |
+
desc_tb = gr.Textbox(label="اضافی تفصیل (اختیاری)", placeholder="مسئلے کو بیان کریں...", lines=3)
|
| 1273 |
+
language_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="رپورٹ اور آواز کی زبان")
|
| 1274 |
+
tts_cb = gr.Checkbox(label="رپورٹ اونچی آواز میں پڑھیں (Text-to-Speech)", value=False)
|
| 1275 |
+
submit_btn = gr.Button("شکایت جمع کریں", variant="primary", size="lg")
|
|
|
|
|
|
|
|
|
|
| 1276 |
|
| 1277 |
+
# ── دائیں: outputs ────────────────────────
|
| 1278 |
with gr.Column(scale=2, min_width=320):
|
| 1279 |
|
| 1280 |
+
gr.HTML('<div class="card-title">AI تجزیہ کے نتائج</div>')
|
| 1281 |
+
annotated_out = gr.Image(label="AI ڈیٹیکشن نتیجہ", height=240)
|
| 1282 |
+
complaint_id_out = gr.Textbox(label="شکایت نمبر", interactive=False)
|
| 1283 |
|
| 1284 |
+
gr.HTML('<div class="card-title" style="margin-top:14px">سرکاری شکایت رپورٹ</div>')
|
| 1285 |
report_out = gr.Textbox(
|
| 1286 |
+
label="مکمل شکایت رپورٹ (جمع کرانے کے لیے تیار)",
|
| 1287 |
lines=22, interactive=False,
|
| 1288 |
+
placeholder="جمع کرانے کے بعد رپورٹ یہاں آئے گی..."
|
| 1289 |
)
|
| 1290 |
wa_out = gr.Markdown()
|
| 1291 |
+
report_tts_out = gr.Audio(label="رپورٹ آڈیو (منتخب زبان میں)", autoplay=False)
|
| 1292 |
|
| 1293 |
+
gr.HTML('<div class="card-title" style="margin-top:16px">قانونی مشورہ</div>')
|
| 1294 |
+
gr.HTML('<div class="info-box">پاکستانی شہری قانون کی بنیاد پر AI کا قانونی رہنمائی۔</div>')
|
| 1295 |
legal_advice_out = gr.Textbox(
|
| 1296 |
+
label="قانونی حقوق اور اقدامات",
|
| 1297 |
lines=12, interactive=False,
|
| 1298 |
+
placeholder="قانونی مشورہ یہاں آئے گا..."
|
| 1299 |
)
|
| 1300 |
+
advice_tts_out = gr.Audio(label="قانونی مشورہ آڈیو (منتخب زبان میں)", autoplay=False)
|
| 1301 |
|
|
|
|
| 1302 |
city_dd.change(fn=update_areas, inputs=[city_dd], outputs=[area_dd])
|
| 1303 |
submit_btn.click(
|
| 1304 |
fn=make_report,
|
|
|
|
| 1309 |
)
|
| 1310 |
|
| 1311 |
# ══════════════════════════════════════════════════
|
| 1312 |
+
# TAB 2 — قانون اور چیٹ بوٹ
|
| 1313 |
# ══════════════════════════════════════════════════
|
| 1314 |
+
with gr.Tab("⚖️ قانون اور چیٹ بوٹ"):
|
| 1315 |
|
| 1316 |
+
gr.HTML('<div class="card-title">پاکستانی شہری قوانین کا ڈیٹا بیس</div>')
|
| 1317 |
with gr.Row():
|
| 1318 |
+
law_issue_dd = gr.Dropdown(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="مسئلہ منتخب کریں", scale=1)
|
| 1319 |
+
law_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="زبان", scale=1)
|
| 1320 |
law_out = gr.Markdown()
|
| 1321 |
+
gr.Button("قانونی تفصیل دکھائیں", variant="primary").click(
|
| 1322 |
fn=law_info, inputs=[law_issue_dd, law_lang_dd], outputs=[law_out]
|
| 1323 |
)
|
| 1324 |
gr.HTML("""<div class="info-box">
|
| 1325 |
+
<strong>فوری ہیلپ لائنیں:</strong>
|
| 1326 |
+
کچرا: <span class="hotline-pill">1139</span> |
|
| 1327 |
+
سڑکیں/NHA: <span class="hotline-pill">051-9032800</span> |
|
| 1328 |
+
WASA لاہور: <span class="hotline-pill">042-99200300</span> |
|
| 1329 |
+
CM پورٹل: <span class="hotline-pill">0800-02345</span> |
|
| 1330 |
+
وفاقی محتسب: <span class="hotline-pill">051-9204551</span>
|
| 1331 |
</div>""")
|
| 1332 |
|
| 1333 |
+
gr.HTML('<div class="card-title" style="margin-top:24px">🤖 RAG قانونی چیٹ بوٹ</div>')
|
| 1334 |
gr.HTML("""<div class="info-box">
|
| 1335 |
+
یہ چیٹ بوٹ <strong>RAG (Retrieval-Augmented Generation)</strong> استعمال کرتا ہے۔
|
| 1336 |
+
پانی، WASA، پائپ لیکیج، کچرا، سڑک کے گڑھے، یا پاکستانی شہری قوانین کے بارے میں پوچھیں۔
|
| 1337 |
+
اب آپ آواز سے بھی سوال پوچھ سکتے ہیں اور جواب سن سکتے ہیں۔
|
| 1338 |
</div>""")
|
| 1339 |
+
|
| 1340 |
+
chat_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="جواب کی زبان")
|
| 1341 |
+
chatbot = gr.Chatbot(label="رہبر قانونی معاون", height=420, value=[])
|
| 1342 |
+
|
| 1343 |
+
# ── Chat input row ────────────────────────────
|
| 1344 |
with gr.Row():
|
| 1345 |
chat_input = gr.Textbox(
|
| 1346 |
+
label="آپ کا سوال",
|
| 1347 |
+
placeholder="مثلاً: WASA نے 3 دن میں پائپ ٹھیک نہیں کیا — میرے کیا حقوق ہیں؟",
|
| 1348 |
lines=2, scale=4
|
| 1349 |
)
|
| 1350 |
+
chat_send_btn = gr.Button("بھیجیں", variant="primary", scale=1)
|
| 1351 |
+
|
| 1352 |
+
# ── Voice input for chatbot ───────────────────
|
| 1353 |
+
gr.HTML('<div class="card-title" style="margin-top:12px">🎤 آواز سے سوال پوچھیں</div>')
|
| 1354 |
+
with gr.Row():
|
| 1355 |
+
chat_audio_in = gr.Audio(
|
| 1356 |
+
type="filepath",
|
| 1357 |
+
label="آواز ریکارڈ کریں",
|
| 1358 |
+
sources=["microphone", "upload"],
|
| 1359 |
+
scale=3,
|
| 1360 |
+
)
|
| 1361 |
+
chat_voice_btn = gr.Button("🎤 آواز سے سوال بھیجیں", variant="secondary", scale=1)
|
| 1362 |
+
|
| 1363 |
+
# ── Chatbot TTS output ────────────────────────
|
| 1364 |
+
gr.HTML('<div class="card-title" style="margin-top:12px">🔊 آواز میں جواب سنیں</div>')
|
| 1365 |
+
with gr.Row():
|
| 1366 |
+
chat_tts_out = gr.Audio(label="چیٹ بوٹ کا آواز جواب", autoplay=False, scale=3)
|
| 1367 |
+
chat_tts_btn = gr.Button("🔊 آخری جواب سنیں", variant="secondary", scale=1)
|
| 1368 |
+
|
| 1369 |
+
# ── Examples ──────────────────────────────────
|
| 1370 |
+
gr.Examples(
|
| 1371 |
+
examples=[
|
| 1372 |
+
["WASA نے 3 دن میں لیکیج ٹھیک نہیں کی — میرے کیا حقوق ہیں؟"],
|
| 1373 |
+
["میرے علاقے کا پانی آلودہ ہے — کہاں شکایت کروں؟"],
|
| 1374 |
+
["ایک ہفتے سے کچرا نہیں اٹھا — کون سا قانون لاگو ہوتا ہے؟"],
|
| 1375 |
+
["ادارہ شکایت نظرانداز کرے تو کیا کریں؟"],
|
| 1376 |
+
["گڑھے سے گاڑی کو نقصان ہوا — کیا معاوضہ مل سکتا ہے؟"],
|
| 1377 |
+
["Pakistan Citizen Portal پر شکایت کیسے کریں؟"],
|
| 1378 |
+
],
|
| 1379 |
+
inputs=chat_input,
|
| 1380 |
+
label="یہ سوال آزمائیں"
|
| 1381 |
+
)
|
| 1382 |
|
| 1383 |
+
# ── Event wiring for chatbot ──────────────────
|
| 1384 |
chat_send_btn.click(
|
| 1385 |
fn=legal_chatbot_rag,
|
| 1386 |
inputs=[chat_input, chatbot, chat_lang_dd],
|
|
|
|
| 1391 |
inputs=[chat_input, chatbot, chat_lang_dd],
|
| 1392 |
outputs=[chatbot, chat_input]
|
| 1393 |
)
|
| 1394 |
+
|
| 1395 |
+
def voice_then_send(audio_file, history, language):
|
| 1396 |
+
"""Transcribe voice, send to chatbot, return updated history"""
|
| 1397 |
+
if audio_file is None:
|
| 1398 |
+
return history, ""
|
| 1399 |
+
transcribed = stt(audio_file)
|
| 1400 |
+
if not transcribed or transcribed.startswith("آڈیو") or transcribed.startswith("ٹرانسکرپشن"):
|
| 1401 |
+
return history, transcribed
|
| 1402 |
+
return legal_chatbot_rag(transcribed, history, language)[0], ""
|
| 1403 |
+
|
| 1404 |
+
chat_voice_btn.click(
|
| 1405 |
+
fn=voice_then_send,
|
| 1406 |
+
inputs=[chat_audio_in, chatbot, chat_lang_dd],
|
| 1407 |
+
outputs=[chatbot, chat_input]
|
| 1408 |
+
)
|
| 1409 |
+
chat_tts_btn.click(
|
| 1410 |
+
fn=chatbot_tts_output,
|
| 1411 |
+
inputs=[chatbot, chat_lang_dd],
|
| 1412 |
+
outputs=[chat_tts_out]
|
| 1413 |
)
|
| 1414 |
|
| 1415 |
# ══════════════════════════════════════════════════
|
| 1416 |
+
# TAB 3 — آواز کا ان پٹ / STT
|
| 1417 |
# ══════════════════════════════════════════════════
|
| 1418 |
+
with gr.Tab("🎤 آواز کا ان پٹ"):
|
| 1419 |
|
| 1420 |
+
gr.HTML('<div class="card-title">تقریر سے متن (Speech-to-Text)</div>')
|
| 1421 |
gr.HTML("""<div class="info-box">
|
| 1422 |
+
اپنی شکایت یا تفصیل نیچے مائیکروفون سے ریکارڈ کریں۔
|
| 1423 |
+
ٹرانسکرپشن <strong>Groq Whisper large-v3</strong> (اگر GROQ_API_KEY سیٹ ہو)
|
| 1424 |
+
یا Google Speech Recognition (متبادل) سے ہوگی۔<br>
|
| 1425 |
+
اردو، انگریزی، پنجابی اور سندھی میں بولیں۔
|
| 1426 |
</div>""")
|
| 1427 |
gr.HTML("""<div class="warn-box">
|
| 1428 |
+
<strong>تجویز:</strong> واضح آواز میں بولیں، شور کم ہو۔
|
| 1429 |
+
ٹرانسکرپشن کے بعد متن کاپی کریں اور "شکایت درج کریں" ٹیب میں پیسٹ کریں۔
|
| 1430 |
</div>""")
|
| 1431 |
audio_in = gr.Audio(
|
| 1432 |
type="filepath",
|
| 1433 |
+
label="آڈیو ریکارڈ یا اپلوڈ کریں",
|
| 1434 |
sources=["microphone", "upload"],
|
| 1435 |
)
|
| 1436 |
+
stt_btn = gr.Button("آڈیو کو متن میں تبدیل کریں", variant="primary")
|
| 1437 |
stt_out = gr.Textbox(
|
| 1438 |
+
label="ٹرانسکرپشن (قابل ترمیم — شکایت ٹیب میں کاپی کریں)",
|
| 1439 |
lines=6, interactive=True,
|
| 1440 |
+
placeholder="ٹرانسکرپٹ شدہ متن یہاں آئے گا..."
|
| 1441 |
)
|
| 1442 |
stt_btn.click(fn=stt, inputs=[audio_in], outputs=[stt_out])
|
| 1443 |
|
| 1444 |
+
gr.HTML('<div class="card-title" style="margin-top:24px">متن سے آواز (TTS) ٹیسٹ</div>')
|
| 1445 |
+
gr.HTML('<div class="info-box">کسی بھی زبان میں آواز آزمائیں۔</div>')
|
| 1446 |
with gr.Row():
|
| 1447 |
+
tts_text_in = gr.Textbox(label="سننے کے لیے متن درج کریں", placeholder="کچھ لکھیں...", scale=3)
|
| 1448 |
+
tts_lang_in = gr.Dropdown(choices=LANGUAGES, value="English", label="زبان", scale=1)
|
| 1449 |
+
tts_test_btn = gr.Button("▶ چلائیں", variant="secondary")
|
| 1450 |
+
tts_test_out = gr.Audio(label="آڈیو آؤٹ پٹ", autoplay=True)
|
| 1451 |
tts_test_btn.click(fn=make_tts, inputs=[tts_text_in, tts_lang_in], outputs=[tts_test_out])
|
| 1452 |
|
| 1453 |
# ══════════════════════════════════════════════════
|
| 1454 |
+
# TAB 4 — ایڈمن ڈیش بورڈ
|
| 1455 |
# ══════════════════════════════════════════════════
|
| 1456 |
+
with gr.Tab("📊 ایڈمن ڈیش بورڈ"):
|
| 1457 |
|
| 1458 |
+
gr.HTML('<div class="card-title">شکایات کے اعداد و شمار</div>')
|
| 1459 |
+
refresh_btn = gr.Button("اعداد و شمار تازہ کریں", variant="primary")
|
| 1460 |
with gr.Row():
|
| 1461 |
stats_out = gr.Markdown()
|
| 1462 |
log_out = gr.Markdown()
|
| 1463 |
refresh_btn.click(fn=get_admin_stats, outputs=[stats_out, log_out])
|
| 1464 |
|
| 1465 |
gr.HTML("""<div class="info-box" style="margin-top:16px">
|
| 1466 |
+
<strong>RAG نالج بیس:</strong>
|
| 1467 |
Road Issues Detection Dataset | Urban Issues Dataset | Consumer Complaints Dataset<br>
|
| 1468 |
+
<strong>AI ماڈلز:</strong>
|
| 1469 |
Gemini 2.0 Flash | YOLOv8 | Llama 3.3 70B | Whisper large-v3 (Groq)<br>
|
| 1470 |
+
<strong>ویکٹر سرچ:</strong> TF-IDF Cosine Similarity (char n-gram, کثیر زبان)
|
| 1471 |
</div>""")
|
| 1472 |
|
| 1473 |
return demo
|
|
|
|
| 1475 |
|
| 1476 |
# ─── LAUNCH ───────────────────────────────────────────────────
|
| 1477 |
if __name__ == "__main__":
|
| 1478 |
+
print("Rahbar v5.0 شروع ہو رہا ہے...")
|
| 1479 |
+
print("RAG Engine:", "تیار" if rag_engine._initialized else "شروع ہو رہا ہے...")
|
| 1480 |
demo = build_ui()
|
| 1481 |
demo.launch(
|
| 1482 |
server_name="0.0.0.0",
|