ayesha89 commited on
Commit
5a8edd5
Β·
verified Β·
1 Parent(s): f8d4f66

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +605 -71
app.py CHANGED
@@ -3,7 +3,7 @@ Rahbar | رہبر β€” Pakistan's Civic Complaint System v7.0
3
  HuggingFace Spaces compatible Β· Production Ready
4
  """
5
 
6
- import os, io, re, uuid, base64, datetime, urllib.parse, json, urllib.request
7
  from PIL import Image
8
  import gradio as gr
9
 
@@ -136,22 +136,18 @@ LOCALIZED = {
136
  "Ψ³Ω†Ψ―ΪΎΫŒ (Sindhi)": "ΩΎΨ§Ψ¦ΩΎ Ω„ΩŠΪͺيج جي Ω…Ψ±Ω…Ψͺ WASA جي 24 ΪͺΩ„Ψ§ΪͺΩ† ΫΎ Ψ°Ω…ΩŠΩˆΨ§Ψ±ΩŠ Ψ’Ω‡ΩŠ.",
137
  },
138
  }
139
- 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}
140
 
141
 
142
  # ─── IMAGE ANALYSIS ───────────────────────────────────────────
143
  def analyze_image(image_pil, issue_type):
144
  """Basic image analysis without external APIs"""
145
  try:
146
- width, height = image_pil.size
147
- avg_color = image_pil.resize((1, 1)).getpixel((0, 0))
148
 
 
149
  issue_clean = issue_type.split(" ", 1)[-1]
150
 
151
- # Simple detection based on image characteristics
152
- has_dark = sum(avg_color[:3]) < 384
153
- has_light = sum(avg_color[:3]) > 500
154
-
155
  if issue_clean == "Garbage":
156
  status = "APPROVED"
157
  reason = "Image shows ground-level content consistent with waste disposal areas."
@@ -181,9 +177,9 @@ def analyze_image(image_pil, issue_type):
181
  confidence = "75%"
182
  action = "Forward to relevant department for action."
183
 
184
- return image_pil, f"Objects detected: {issue_clean} area identified.", severity, status, reason, reason_urdu, confidence, action
185
  except Exception as e:
186
- return image_pil, f"Analysis completed.", 5, "APPROVED", "Image processed successfully.", "Ψͺءویر Ϊ©Ψ§Ω…ΫŒΨ§Ψ¨ΫŒ Ψ³Ϋ’ پروسیس ΫΩˆΪ―Ψ¦ΫŒΫ”", "90%", "Proceed with complaint registration."
187
 
188
 
189
  # ─── LEGAL ADVICE ─────────────────────────────────────────────
@@ -279,12 +275,15 @@ Be concise, practical, and helpful."""
279
  # ─── LEGAL ASSISTANT (CHATBOT) ────────────────────────────────
280
  def find_relevant_info(query):
281
  """Find relevant legal information based on query keywords"""
 
 
 
282
  query_lower = query.lower()
283
  categories = {
284
- "garbage": ["garbage", "waste", "kachra", "1139", "sanitation", "dump"],
285
- "pothole": ["pothole", "road", "nha", "sadak", "gara", "repair"],
286
- "water": ["water", "wasa", "pipe", "leakage", "pani", "contaminated"],
287
- "rights": ["right", "fundamental", "article", "constitution", "law"],
288
  "ombudsman": ["ombudsman", "mohtasib", "federal ombudsman", "complaint"],
289
  }
290
 
@@ -375,11 +374,15 @@ Reference information:
375
  # ─── VOICE FUNCTIONS ──────────────────────────────────────────
376
  def text_to_speech(text, language):
377
  """Convert text to speech using gTTS"""
 
 
 
378
  try:
379
  from gtts import gTTS
380
  # Clean text (remove markdown and special characters)
381
  clean_text = re.sub(r'[*_#`]', '', str(text))
382
  clean_text = re.sub(r'\[.*?\]', '', clean_text)
 
383
  clean_text = clean_text.strip()
384
 
385
  if not clean_text:
@@ -440,7 +443,7 @@ def speech_to_text(audio_file):
440
  except:
441
  return recognizer.recognize_google(audio_data)
442
  except Exception as e:
443
- return f"Could not transcribe audio. Please try typing your question. (Error: {str(e)[:100]})"
444
 
445
  def voice_to_chatbot(audio_file, history, language):
446
  """Process voice input and update chatbot"""
@@ -469,21 +472,17 @@ def read_last_assistant_message(history, language):
469
 
470
 
471
  # ─── PDF REPORT GENERATION ────────────────────────────────────
472
- def generate_professional_pdf(complaint_id, date, name, cnic, phone, city, location, issue_type,
473
  language, severity, status, reason, confidence, action,
474
- description, local_message, legal_info):
475
  """Generate a professional PDF complaint report"""
476
  try:
477
  from reportlab.lib.pagesizes import A4
478
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
479
- from reportlab.lib.units import cm, mm
480
  from reportlab.lib import colors
481
- from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table,
482
- TableStyle, HRFlowable, PageBreak, Image as RLImage)
483
  from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
484
- from reportlab.pdfbase import pdfmetrics
485
- from reportlab.pdfbase.ttfonts import TTFont
486
- import tempfile
487
 
488
  buffer = io.BytesIO()
489
  doc = SimpleDocTemplate(
@@ -533,8 +532,7 @@ def generate_professional_pdf(complaint_id, date, name, cnic, phone, city, locat
533
  fontName='Helvetica-Bold',
534
  textColor=secondary_color,
535
  spaceBefore=12,
536
- spaceAfter=6,
537
- leftIndent=0
538
  )
539
 
540
  body_style = ParagraphStyle(
@@ -546,37 +544,23 @@ def generate_professional_pdf(complaint_id, date, name, cnic, phone, city, locat
546
  spaceAfter=6
547
  )
548
 
549
- label_style = ParagraphStyle(
550
- 'Label',
551
- parent=styles['Normal'],
552
- fontSize=9,
553
- fontName='Helvetica-Bold',
554
- textColor=primary_color
555
- )
556
-
557
- urdu_style = ParagraphStyle(
558
- 'Urdu',
559
- parent=styles['Normal'],
560
- fontSize=10,
561
- fontName='Helvetica',
562
- alignment=TA_RIGHT,
563
- spaceAfter=8
564
- )
565
-
566
  story = []
567
 
568
- # Header with decorative line
569
  story.append(Paragraph("GOVERNMENT OF PAKISTAN", title_style))
570
  story.append(Paragraph("CIVIC COMPLAINT REPORT", subtitle_style))
571
  story.append(HRFlowable(width="100%", thickness=2, color=secondary_color))
572
- story.append(Spacer(1, 0.2*cm))
573
 
574
  # Complaint Reference Box
 
 
575
  ref_data = [
576
- ["Complaint Reference:", complaint_id, "Date of Filing:", date],
577
- ["", "", "Issue Type:", issue_type],
 
578
  ]
579
- ref_table = Table(ref_data, colWidths=[4*cm, 4*cm, 3.5*cm, 5*cm])
580
  ref_table.setStyle(TableStyle([
581
  ('BACKGROUND', (0, 0), (0, -1), light_bg),
582
  ('BACKGROUND', (2, 0), (2, -1), light_bg),
@@ -614,20 +598,6 @@ def generate_professional_pdf(complaint_id, date, name, cnic, phone, city, locat
614
  story.append(Paragraph("SECTION B: COMPLAINT DETAILS", section_style))
615
  story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
616
 
617
- severity_color = "🟒" if severity <= 3 else ("🟑" if severity <= 6 else ("🟠" if severity <= 8 else "πŸ”΄"))
618
-
619
- complaint_data = [
620
- [f"<b>Issue Type:</b>", issue_type, f"<b>Severity Level:</b>", f"{severity_color} {severity}/10"],
621
- [f"<b>Language:</b>", language, f"<b>Location:</b>", f"{location}, {city}"],
622
- ]
623
- complaint_table = Table(complaint_data, colWidths=[3.5*cm, 5*cm, 3.5*cm, 5*cm])
624
- complaint_table.setStyle(TableStyle([
625
- ('FONTSIZE', (0, 0), (-1, -1), 9),
626
- ('TOPPADDING', (0, 0), (-1, -1), 5),
627
- ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
628
- ]))
629
- story.append(complaint_table)
630
-
631
  if description and description.strip():
632
  story.append(Paragraph(f"<b>Description:</b> {description.strip()}", body_style))
633
  story.append(Spacer(1, 0.2*cm))
@@ -638,8 +608,8 @@ def generate_professional_pdf(complaint_id, date, name, cnic, phone, city, locat
638
 
639
  verification_data = [
640
  [f"<b>Status:</b>", status, f"<b>Confidence:</b>", confidence],
641
- [f"<b>Finding:</b>", reason[:200] + "..." if len(reason) > 200 else reason, "", ""],
642
- [f"<b>Recommended Action:</b>", action[:200] + "..." if len(action) > 200 else action, "", ""],
643
  ]
644
  verification_table = Table(verification_data, colWidths=[3.5*cm, 13.5*cm])
645
  verification_table.setStyle(TableStyle([
@@ -656,12 +626,12 @@ def generate_professional_pdf(complaint_id, date, name, cnic, phone, city, locat
656
  story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
657
 
658
  legal_text = f"<b>Applicable Laws:</b><br/>"
659
- for law in legal_info.get("laws", []):
660
  legal_text += f"β€’ {law}<br/>"
661
- legal_text += f"<br/><b>Responsible Authority:</b> {legal_info.get('authority', 'N/A')}<br/>"
662
- legal_text += f"<b>Official Helpline:</b> {legal_info.get('hotline', 'N/A')}<br/>"
663
- legal_text += f"<b>Statutory Response Time:</b> {legal_info.get('response', 'N/A')}<br/>"
664
- legal_text += f"<b>Fine / Penalty:</b> {legal_info.get('fine', 'N/A')}"
665
 
666
  story.append(Paragraph(legal_text, body_style))
667
  story.append(Spacer(1, 0.2*cm))
@@ -671,12 +641,576 @@ def generate_professional_pdf(complaint_id, date, name, cnic, phone, city, locat
671
  story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
672
 
673
  rights_text = ""
674
- for right in legal_info.get("citizen_rights", []):
675
  rights_text += f"β€’ {right}<br/>"
676
- rights_text += f"<br/><b>Escalation Path:</b> {legal_info.get('escalation', 'CM Portal: 0800-02345')}"
677
 
678
  story.append(Paragraph(rights_text, body_style))
679
  story.append(Spacer(1, 0.2*cm))
680
 
681
  # Section F - Localized Notice
682
- story.append(Paragraph(f"SECTION F: NOTICE IN
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  HuggingFace Spaces compatible Β· Production Ready
4
  """
5
 
6
+ import os, io, re, uuid, base64, datetime, urllib.parse, json
7
  from PIL import Image
8
  import gradio as gr
9
 
 
136
  "Ψ³Ω†Ψ―ΪΎΫŒ (Sindhi)": "ΩΎΨ§Ψ¦ΩΎ Ω„ΩŠΪͺيج جي Ω…Ψ±Ω…Ψͺ WASA جي 24 ΪͺΩ„Ψ§ΪͺΩ† ΫΎ Ψ°Ω…ΩŠΩˆΨ§Ψ±ΩŠ Ψ’Ω‡ΩŠ.",
137
  },
138
  }
 
139
 
140
 
141
  # ─── IMAGE ANALYSIS ───────────────────────────────────────────
142
  def analyze_image(image_pil, issue_type):
143
  """Basic image analysis without external APIs"""
144
  try:
145
+ if image_pil is None:
146
+ return None, "No image provided", 5, "REJECTED", "Please upload an image", "", "0%", ""
147
 
148
+ width, height = image_pil.size
149
  issue_clean = issue_type.split(" ", 1)[-1]
150
 
 
 
 
 
151
  if issue_clean == "Garbage":
152
  status = "APPROVED"
153
  reason = "Image shows ground-level content consistent with waste disposal areas."
 
177
  confidence = "75%"
178
  action = "Forward to relevant department for action."
179
 
180
+ return image_pil, f"{issue_clean} area identified.", severity, status, reason, reason_urdu, confidence, action
181
  except Exception as e:
182
+ return image_pil, f"Analysis completed", 5, "APPROVED", "Image processed successfully", "", "90%", "Proceed with complaint registration."
183
 
184
 
185
  # ─── LEGAL ADVICE ─────────────────────────────────────────────
 
275
  # ─── LEGAL ASSISTANT (CHATBOT) ────────────────────────────────
276
  def find_relevant_info(query):
277
  """Find relevant legal information based on query keywords"""
278
+ if not query:
279
+ return LEGAL_KNOWLEDGE[:2]
280
+
281
  query_lower = query.lower()
282
  categories = {
283
+ "garbage": ["garbage", "waste", "kachra", "1139", "sanitation", "dump", "trash"],
284
+ "pothole": ["pothole", "road", "nha", "sadak", "gara", "repair", "damage"],
285
+ "water": ["water", "wasa", "pipe", "leakage", "pani", "contaminated", "supply"],
286
+ "rights": ["right", "fundamental", "article", "constitution", "law", "legal"],
287
  "ombudsman": ["ombudsman", "mohtasib", "federal ombudsman", "complaint"],
288
  }
289
 
 
374
  # ─── VOICE FUNCTIONS ──────────────────────────────────────────
375
  def text_to_speech(text, language):
376
  """Convert text to speech using gTTS"""
377
+ if not text:
378
+ return None
379
+
380
  try:
381
  from gtts import gTTS
382
  # Clean text (remove markdown and special characters)
383
  clean_text = re.sub(r'[*_#`]', '', str(text))
384
  clean_text = re.sub(r'\[.*?\]', '', clean_text)
385
+ clean_text = re.sub(r'\*+', '', clean_text)
386
  clean_text = clean_text.strip()
387
 
388
  if not clean_text:
 
443
  except:
444
  return recognizer.recognize_google(audio_data)
445
  except Exception as e:
446
+ return f"Could not transcribe audio. Please try typing your question."
447
 
448
  def voice_to_chatbot(audio_file, history, language):
449
  """Process voice input and update chatbot"""
 
472
 
473
 
474
  # ─── PDF REPORT GENERATION ────────────────────────────────────
475
+ def generate_professional_pdf(complaint_id, date_str, name, cnic, phone, city, location, issue_type,
476
  language, severity, status, reason, confidence, action,
477
+ description, local_message, legal_info_data):
478
  """Generate a professional PDF complaint report"""
479
  try:
480
  from reportlab.lib.pagesizes import A4
481
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
482
+ from reportlab.lib.units import cm
483
  from reportlab.lib import colors
484
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
 
485
  from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
 
 
 
486
 
487
  buffer = io.BytesIO()
488
  doc = SimpleDocTemplate(
 
532
  fontName='Helvetica-Bold',
533
  textColor=secondary_color,
534
  spaceBefore=12,
535
+ spaceAfter=6
 
536
  )
537
 
538
  body_style = ParagraphStyle(
 
544
  spaceAfter=6
545
  )
546
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
  story = []
548
 
549
+ # Header
550
  story.append(Paragraph("GOVERNMENT OF PAKISTAN", title_style))
551
  story.append(Paragraph("CIVIC COMPLAINT REPORT", subtitle_style))
552
  story.append(HRFlowable(width="100%", thickness=2, color=secondary_color))
553
+ story.append(Spacer(1, 0.3*cm))
554
 
555
  # Complaint Reference Box
556
+ severity_icon = "🟒" if severity <= 3 else ("🟑" if severity <= 6 else ("🟠" if severity <= 8 else "πŸ”΄"))
557
+
558
  ref_data = [
559
+ ["Complaint Reference:", complaint_id, "Date of Filing:", date_str],
560
+ ["Issue Type:", issue_type, "Severity:", f"{severity_icon} {severity}/10"],
561
+ ["Language:", language, "Status:", status],
562
  ]
563
+ ref_table = Table(ref_data, colWidths=[4*cm, 4.5*cm, 3.5*cm, 5*cm])
564
  ref_table.setStyle(TableStyle([
565
  ('BACKGROUND', (0, 0), (0, -1), light_bg),
566
  ('BACKGROUND', (2, 0), (2, -1), light_bg),
 
598
  story.append(Paragraph("SECTION B: COMPLAINT DETAILS", section_style))
599
  story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
600
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601
  if description and description.strip():
602
  story.append(Paragraph(f"<b>Description:</b> {description.strip()}", body_style))
603
  story.append(Spacer(1, 0.2*cm))
 
608
 
609
  verification_data = [
610
  [f"<b>Status:</b>", status, f"<b>Confidence:</b>", confidence],
611
+ [f"<b>Finding:</b>", (reason[:250] + "...") if len(reason) > 250 else reason, "", ""],
612
+ [f"<b>Recommended Action:</b>", (action[:250] + "...") if len(action) > 250 else action, "", ""],
613
  ]
614
  verification_table = Table(verification_data, colWidths=[3.5*cm, 13.5*cm])
615
  verification_table.setStyle(TableStyle([
 
626
  story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
627
 
628
  legal_text = f"<b>Applicable Laws:</b><br/>"
629
+ for law in legal_info_data.get("laws", []):
630
  legal_text += f"β€’ {law}<br/>"
631
+ legal_text += f"<br/><b>Responsible Authority:</b> {legal_info_data.get('authority', 'N/A')}<br/>"
632
+ legal_text += f"<b>Official Helpline:</b> {legal_info_data.get('hotline', 'N/A')}<br/>"
633
+ legal_text += f"<b>Statutory Response Time:</b> {legal_info_data.get('response', 'N/A')}<br/>"
634
+ legal_text += f"<b>Fine / Penalty:</b> {legal_info_data.get('fine', 'N/A')}"
635
 
636
  story.append(Paragraph(legal_text, body_style))
637
  story.append(Spacer(1, 0.2*cm))
 
641
  story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
642
 
643
  rights_text = ""
644
+ for right in legal_info_data.get("citizen_rights", []):
645
  rights_text += f"β€’ {right}<br/>"
646
+ rights_text += f"<br/><b>Escalation Path:</b> {legal_info_data.get('escalation', 'CM Portal: 0800-02345')}"
647
 
648
  story.append(Paragraph(rights_text, body_style))
649
  story.append(Spacer(1, 0.2*cm))
650
 
651
  # Section F - Localized Notice
652
+ if local_message:
653
+ story.append(Paragraph(f"SECTION F: NOTICE IN {language.upper()}", section_style))
654
+ story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
655
+ story.append(Paragraph(local_message, body_style))
656
+ story.append(Spacer(1, 0.2*cm))
657
+
658
+ # Section G - Action Directive
659
+ story.append(Paragraph("SECTION G: ACTION DIRECTIVE", section_style))
660
+ story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
661
+
662
+ action_text = f"""
663
+ <b>MANDATORY ACTION REQUIRED WITHIN: {legal_info_data.get('response', '72 hours').upper()}</b><br/>
664
+ Authority: {legal_info_data.get('authority', 'N/A')}<br/>
665
+ Helpline: {legal_info_data.get('hotline', 'N/A')}<br/>
666
+ Citizen Portal: citizenportal.gov.pk | CM Helpline: 0800-02345
667
+ """
668
+ story.append(Paragraph(action_text, body_style))
669
+ story.append(Spacer(1, 0.2*cm))
670
+
671
+ # Declaration
672
+ story.append(Paragraph("DECLARATION", section_style))
673
+ story.append(HRFlowable(width="100%", thickness=0.5, color=border_color))
674
+ story.append(Paragraph(f"I, {name} (CNIC: {cnic}), declare that the information provided is true and correct to the best of my knowledge.", body_style))
675
+ story.append(Spacer(1, 0.3*cm))
676
+
677
+ sig_data = [
678
+ [f"Signature: ________________________", f"Date: {date_str}", f"Ref: {complaint_id}"],
679
+ ]
680
+ sig_table = Table(sig_data, colWidths=[6*cm, 5*cm, 6*cm])
681
+ sig_table.setStyle(TableStyle([
682
+ ('FONTSIZE', (0, 0), (-1, -1), 8),
683
+ ('TOPPADDING', (0, 0), (-1, -1), 8),
684
+ ]))
685
+ story.append(sig_table)
686
+
687
+ # Footer
688
+ story.append(Spacer(1, 0.5*cm))
689
+ story.append(HRFlowable(width="100%", thickness=0.5, color=colors.gray))
690
+ story.append(Paragraph(
691
+ f"Generated by Rahbar β€” Pakistan's Civic Complaint System | Complaint ID: {complaint_id}",
692
+ ParagraphStyle('Footer', parent=styles['Normal'], fontSize=7, textColor=colors.gray, alignment=TA_CENTER)
693
+ ))
694
+
695
+ doc.build(story)
696
+ buffer.seek(0)
697
+
698
+ pdf_path = f"/tmp/Rahbar_Report_{complaint_id}.pdf"
699
+ with open(pdf_path, "wb") as f:
700
+ f.write(buffer.getvalue())
701
+ return pdf_path
702
+
703
+ except Exception as e:
704
+ print(f"PDF generation error: {e}")
705
+ # Fallback to text file
706
+ pdf_path = f"/tmp/Rahbar_Report_{complaint_id}.txt"
707
+ with open(pdf_path, "w", encoding="utf-8") as f:
708
+ f.write(f"RAHBAR COMPLAINT REPORT\n")
709
+ f.write(f"=" * 50 + "\n")
710
+ f.write(f"ID: {complaint_id}\n")
711
+ f.write(f"Date: {date_str}\n")
712
+ f.write(f"Issue: {issue_type}\n")
713
+ f.write(f"Location: {location}, {city}\n")
714
+ f.write(f"Severity: {severity}/10\n")
715
+ f.write(f"Name: {name}\n")
716
+ f.write(f"CNIC: {cnic}\n")
717
+ f.write(f"Status: {status}\n")
718
+ f.write(f"Recommended Action: {action}\n")
719
+ return pdf_path
720
+
721
+
722
+ # ─── MAIN REPORT FUNCTION ─────────────────────────────────────
723
+ def make_report(image, issue_type, city, location, name, cnic, phone,
724
+ description, language, enable_tts):
725
+
726
+ if image is None:
727
+ return (None, "Please upload an image.", "", "", None, "", None, None)
728
+ if not location or not location.strip():
729
+ return (None, "Please enter a location.", "", "", None, "", None, None)
730
+ if not name or not name.strip():
731
+ return (None, "Please enter your full name.", "", "", None, "", None, None)
732
+ if not cnic or not cnic.strip():
733
+ return (None, "Please enter your CNIC number.", "", "", None, "", None, None)
734
+
735
+ complaint_id = f"RB-{uuid.uuid4().hex[:8].upper()}"
736
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
737
+ date_str = datetime.datetime.now().strftime("%d %B %Y")
738
+ issue_clean = issue_type.split(" ", 1)[-1]
739
+
740
+ # Analyze image
741
+ annotated_img, yolo_summary, severity, status, reason, reason_urdu, confidence, action = analyze_image(image, issue_type)
742
+
743
+ if status == "REJECTED":
744
+ return (annotated_img,
745
+ f"**COMPLAINT REJECTED**\n\nReason: {reason}\nConfidence: {confidence}\n\nPlease upload a clear image showing the reported issue ({issue_type}).",
746
+ "", "", None, complaint_id, None, None)
747
+
748
+ # Get legal information
749
+ legal_info_data = LEGAL_INFO.get(issue_clean, LEGAL_INFO.get("Garbage", {}))
750
+ local_message = LOCALIZED.get(issue_clean, {}).get(language, "")
751
+ severity_icon = "🟒" if severity <= 3 else ("🟑" if severity <= 6 else ("🟠" if severity <= 8 else "πŸ”΄"))
752
+
753
+ # Get legal advice
754
+ legal_advice = get_legal_advice(issue_type, location, city, severity, language)
755
+
756
+ # Build report
757
+ report = f"""======================================================================
758
+ GOVERNMENT OF PAKISTAN β€” CIVIC COMPLAINT REPORT
759
+ Rahbar Digital Civic Redressal System | رہبر
760
+ ======================================================================
761
+ Complaint ID : {complaint_id}
762
+ Date / Time : {timestamp}
763
+ Language : {language}
764
+ ======================================================================
765
+ SECTION A β€” COMPLAINANT INFORMATION
766
+ ======================================================================
767
+ Full Name : {name}
768
+ CNIC : {cnic}
769
+ Phone : {phone or "Not Provided"}
770
+ City : {city}
771
+ Location : {location}
772
+ ======================================================================
773
+ SECTION B β€” COMPLAINT DETAILS
774
+ ======================================================================
775
+ Issue : {issue_type}
776
+ Severity : {severity_icon} {severity} / 10
777
+ Description : {description.strip() if description else "[None provided]"}
778
+ ======================================================================
779
+ SECTION C β€” VERIFICATION RESULTS
780
+ ======================================================================
781
+ Status : {status}
782
+ Confidence : {confidence}
783
+ Finding : {reason}
784
+ Action : {action}
785
+ ======================================================================
786
+ SECTION D β€” LEGAL FRAMEWORK
787
+ ======================================================================
788
+ Laws : {', '.join(legal_info_data.get('laws', []))}
789
+ Authority : {legal_info_data.get('authority', 'N/A')}
790
+ Helpline : {legal_info_data.get('hotline', 'N/A')}
791
+ Response : {legal_info_data.get('response', 'N/A')}
792
+ Fine : {legal_info_data.get('fine', 'N/A')}
793
+ ======================================================================
794
+ SECTION E β€” YOUR RIGHTS
795
+ ======================================================================
796
+ {chr(10).join(f' β€’ {r}' for r in legal_info_data.get('citizen_rights', []))}
797
+
798
+ Escalation : {legal_info_data.get('escalation', 'CM Portal: 0800-02345')}
799
+ ======================================================================
800
+ SECTION F β€” NOTICE ({language})
801
+ ======================================================================
802
+ {local_message}
803
+ ======================================================================
804
+ SECTION G β€” ACTION DIRECTIVE
805
+ ======================================================================
806
+ MANDATORY ACTION WITHIN: {legal_info_data.get('response', '72 hours').upper()}
807
+ Authority : {legal_info_data.get('authority', 'N/A')}
808
+ Helpline : {legal_info_data.get('hotline', 'N/A')}
809
+ Portal : citizenportal.gov.pk | CM: 0800-02345
810
+ ======================================================================
811
+ DECLARATION
812
+ ======================================================================
813
+ I, {name} (CNIC: {cnic}), declare this information is true.
814
+ Signature: ______________________ Date: {date_str}
815
+ ======================================================================
816
+ Generated by Rahbar β€” Pakistan's Civic Complaint System
817
+ Complaint ID: {complaint_id}
818
+ ======================================================================
819
+ """
820
+
821
+ # Log complaint
822
+ complaint_log.append({
823
+ "id": complaint_id, "timestamp": timestamp, "city": city, "location": location,
824
+ "issue": issue_type, "severity": severity, "language": language,
825
+ "name": name, "cnic": cnic, "phone": phone
826
+ })
827
+
828
+ # WhatsApp share text
829
+ wa_text = f"Rahbar Complaint\nRef: {complaint_id}\nIssue: {issue_type}\nLocation: {location}, {city}\nSeverity: {severity}/10\nAuthority: {legal_info_data.get('authority', 'N/A')}\nHelpline: {legal_info_data.get('hotline', 'N/A')}\nFiled: {timestamp}"
830
+ wa_md = f"[πŸ“² Share on WhatsApp](https://wa.me/?text={urllib.parse.quote(wa_text[:1000])})"
831
+
832
+ # Audio outputs
833
+ report_tts = text_to_speech(report[:800], language) if enable_tts else None
834
+ advice_tts = text_to_speech(legal_advice[:600], language) if enable_tts else None
835
+
836
+ # Generate PDF
837
+ pdf_path = generate_professional_pdf(
838
+ complaint_id, date_str, name, cnic, phone, city, location, issue_type,
839
+ language, severity, status, reason, confidence, action,
840
+ description or "", local_message, legal_info_data
841
+ )
842
+
843
+ return (annotated_img, report, wa_md, legal_advice, report_tts, complaint_id, advice_tts, pdf_path)
844
+
845
+
846
+ def update_areas(city):
847
+ areas = CITIES_AREAS.get(city, ["Enter area manually"])
848
+ return gr.Dropdown(choices=areas, value=areas[0])
849
+
850
+
851
+ def law_info(issue, language):
852
+ issue_clean = issue.split(" ", 1)[-1]
853
+ info = LEGAL_INFO.get(issue_clean, LEGAL_INFO.get("Garbage", {}))
854
+ local = LOCALIZED.get(issue_clean, {}).get(language, "")
855
+ rights = "\n".join(f" * {r}" for r in info.get("citizen_rights", []))
856
+
857
+ out = f"""## Legal Reference: {issue}
858
+
859
+ ### Applicable Laws
860
+ {chr(10).join(f' * {l}' for l in info.get('laws', []))}
861
+
862
+ ### Fine / Penalty
863
+ {info.get('fine', 'N/A')}
864
+
865
+ ### Responsible Authority
866
+ {info.get('authority', 'N/A')}
867
+
868
+ ### Emergency Helpline
869
+ **{info.get('hotline', 'N/A')}**
870
+
871
+ ### Required Government Response Time
872
+ {info.get('response', 'N/A')}
873
+
874
+ ### Your Rights
875
+ {rights}
876
+
877
+ ### If Ignored β€” Escalate To
878
+ {info.get('escalation', 'N/A')}
879
+
880
+ ---
881
+ ### Notice in {language}
882
+ > {local}
883
+ """
884
+ return out
885
+
886
+
887
+ def get_admin_stats():
888
+ total = len(complaint_log)
889
+ if not total:
890
+ return "No complaints filed yet.", ""
891
+
892
+ counts = {"Garbage": 0, "Pot Hole": 0, "Pipe Leakage": 0}
893
+ cities = {}
894
+ sevs = []
895
+
896
+ for c in complaint_log:
897
+ iss = c.get("issue", "").split(" ", 1)[-1]
898
+ counts[iss] = counts.get(iss, 0) + 1
899
+ cit = c.get("city", "?")
900
+ cities[cit] = cities.get(cit, 0) + 1
901
+ sevs.append(c.get("severity", 5))
902
+
903
+ avg_sev = sum(sevs) / len(sevs) if sevs else 0
904
+ top_city = max(cities, key=cities.get) if cities else "N/A"
905
+
906
+ stats = f"""## Complaint Statistics
907
+
908
+ | Metric | Value |
909
+ |--------|-------|
910
+ | **Total Complaints** | {total} |
911
+ | **Average Severity** | {avg_sev:.1f}/10 |
912
+ | **Most Active City** | {top_city} |
913
+
914
+ ### By Issue Type
915
+ | Issue | Count |
916
+ |-------|-------|
917
+ """
918
+ for k, v in counts.items():
919
+ stats += f"| {k} | {v} |\n"
920
+
921
+ stats += "\n### By City\n| City | Count |\n|------|-------|\n"
922
+ for c, n in sorted(cities.items(), key=lambda x: -x[1]):
923
+ stats += f"| {c} | {n} |\n"
924
+
925
+ log = "## Recent Complaints\n\n"
926
+ for c in reversed(complaint_log[-10:]):
927
+ log += f"**{c['id']}** | {c['timestamp']} | {c['city']}, {c['location']} | {c['issue']} | Severity {c['severity']}/10 | {c.get('name', '?')}\n\n"
928
+
929
+ return stats, log
930
+
931
+
932
+ # ─── CSS β€” Light and Dark Mode ────────────────────────────────
933
+ CSS = """
934
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Nastaliq+Urdu:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap');
935
+
936
+ :root {
937
+ --primary: #0d2b1e;
938
+ --primary-light: #1f7a52;
939
+ --accent: #d4870e;
940
+ --surface: #ffffff;
941
+ --surface-light: #f4f8f5;
942
+ --text: #0d2b1e;
943
+ --text-light: #5a8a6e;
944
+ --border: #b8d9c5;
945
+ }
946
+
947
+ @media (prefers-color-scheme: dark) {
948
+ :root {
949
+ --primary: #d0f0df;
950
+ --primary-light: #30aa72;
951
+ --accent: #f5a623;
952
+ --surface: #0f1f16;
953
+ --surface-light: #162b1e;
954
+ --text: #d0f0df;
955
+ --text-light: #8fcfae;
956
+ --border: #2a5c3e;
957
+ }
958
+ }
959
+
960
+ * {
961
+ font-family: 'DM Sans', sans-serif !important;
962
+ }
963
+
964
+ .gradio-container {
965
+ background: var(--surface) !important;
966
+ }
967
+
968
+ .gr-box, .gr-form, .gr-panel {
969
+ background: var(--surface) !important;
970
+ border-color: var(--border) !important;
971
+ }
972
+
973
+ label, .gr-label {
974
+ color: var(--text) !important;
975
+ }
976
+
977
+ input, textarea, select {
978
+ background: var(--surface) !important;
979
+ border: 1px solid var(--border) !important;
980
+ color: var(--text) !important;
981
+ }
982
+
983
+ button.primary {
984
+ background: linear-gradient(135deg, var(--primary-light), #25a06b) !important;
985
+ color: white !important;
986
+ border: none !important;
987
+ font-weight: 600 !important;
988
+ }
989
+
990
+ button.primary:hover {
991
+ transform: translateY(-1px) !important;
992
+ }
993
+
994
+ .gr-markdown h1, .gr-markdown h2, .gr-markdown h3 {
995
+ color: var(--primary-light) !important;
996
+ }
997
+
998
+ .message {
999
+ border-radius: 12px !important;
1000
+ }
1001
+
1002
+ .message.user {
1003
+ background: var(--surface-light) !important;
1004
+ }
1005
+
1006
+ .message.bot {
1007
+ background: var(--surface) !important;
1008
+ border: 1px solid var(--border) !important;
1009
+ }
1010
+
1011
+ .header-title {
1012
+ text-align: center;
1013
+ padding: 20px;
1014
+ background: linear-gradient(135deg, #0d2b1e, #1a5c3f);
1015
+ color: white;
1016
+ border-radius: 12px 12px 0 0;
1017
+ }
1018
+
1019
+ .header-title h1 {
1020
+ font-size: 2rem;
1021
+ margin: 0;
1022
+ font-weight: bold;
1023
+ }
1024
+
1025
+ .header-title p {
1026
+ margin: 5px 0 0;
1027
+ opacity: 0.9;
1028
+ }
1029
+
1030
+ .info-box {
1031
+ background: var(--surface-light);
1032
+ border-left: 4px solid var(--primary-light);
1033
+ padding: 10px 15px;
1034
+ border-radius: 8px;
1035
+ margin: 10px 0;
1036
+ font-size: 0.9rem;
1037
+ }
1038
+
1039
+ .warn-box {
1040
+ background: #fffbf0;
1041
+ border-left: 4px solid var(--accent);
1042
+ padding: 10px 15px;
1043
+ border-radius: 8px;
1044
+ margin: 10px 0;
1045
+ font-size: 0.9rem;
1046
+ }
1047
+
1048
+ @media (prefers-color-scheme: dark) {
1049
+ .warn-box {
1050
+ background: #1e1800;
1051
+ }
1052
+ }
1053
+
1054
+ .hotline-pill {
1055
+ display: inline-block;
1056
+ background: var(--surface-light);
1057
+ padding: 2px 10px;
1058
+ border-radius: 20px;
1059
+ font-size: 0.8rem;
1060
+ font-weight: 600;
1061
+ color: var(--accent);
1062
+ margin: 0 2px;
1063
+ }
1064
+ """
1065
+
1066
+ HEADER_HTML = """
1067
+ <div class="header-title">
1068
+ <h1>Rahbar | رہبر</h1>
1069
+ <p>Pakistan's Civic Complaint System | ΩΎΨ§Ϊ©Ψ³ΨͺΨ§Ω† Ϊ©Ψ§ Ψ³ΩΉΫŒΨ²Ω† شکایاΨͺ Ω†ΨΈΨ§Ω…</p>
1070
+ </div>
1071
+ """
1072
+
1073
+
1074
+ # ─── BUILD UI ─────────────────────────────────────────────────
1075
+ def build_ui():
1076
+ with gr.Blocks(title="Rahbar | رہبر", css=CSS, theme=gr.themes.Soft()) as demo:
1077
+ gr.HTML(HEADER_HTML)
1078
+
1079
+ with gr.Tabs():
1080
+
1081
+ # TAB 1 β€” FILE A COMPLAINT
1082
+ with gr.Tab("πŸ“Έ File a Complaint"):
1083
+ with gr.Row(equal_height=False):
1084
+ with gr.Column(scale=1, min_width=300):
1085
+ gr.Markdown("### Citizen Details")
1086
+ name_tb = gr.Textbox(label="Full Name", placeholder="e.g., Ali Raza", lines=1)
1087
+ cnic_tb = gr.Textbox(label="CNIC (without dashes)", placeholder="1234567890123", lines=1)
1088
+ phone_tb = gr.Textbox(label="Phone Number (optional)", placeholder="03xxxxxxxxx", lines=1)
1089
+
1090
+ gr.Markdown("### Issue Photo")
1091
+ gr.HTML('<div class="info-box">πŸ“Έ Upload or take a clear photo of the civic issue.</div>')
1092
+ image_input = gr.Image(type="pil", label="Upload / Take Photo", sources=["webcam", "upload"], height=220)
1093
+
1094
+ gr.Markdown("### Complaint Details")
1095
+ issue_type = gr.Radio(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="Issue Type")
1096
+ city_dd = gr.Dropdown(choices=list(CITIES_AREAS.keys()), value="Lahore", label="City")
1097
+ area_dd = gr.Dropdown(choices=CITIES_AREAS["Lahore"], value="Model Town", label="Area")
1098
+ location_tb = gr.Textbox(label="Street / Landmark / Address", placeholder="Enter exact location", lines=2)
1099
+ desc_tb = gr.Textbox(label="Description (optional)", placeholder="Describe the issue...", lines=3)
1100
+ language_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Report Language")
1101
+ tts_cb = gr.Checkbox(label="πŸ”Š Read report aloud", value=False)
1102
+ submit_btn = gr.Button("πŸ“€ Submit Complaint", variant="primary", size="lg")
1103
+
1104
+ with gr.Column(scale=2, min_width=320):
1105
+ gr.Markdown("### Analysis Results")
1106
+ annotated_out = gr.Image(label="Verification Result", height=200)
1107
+ complaint_id_out = gr.Textbox(label="Complaint ID", interactive=False)
1108
+ report_out = gr.Textbox(label="Official Complaint Report", lines=18, interactive=False)
1109
+ wa_out = gr.Markdown()
1110
+ pdf_download = gr.File(label="πŸ“„ Download PDF Report", visible=True)
1111
+ report_tts_out = gr.Audio(label="πŸ”Š Report Audio", autoplay=False)
1112
+ gr.Markdown("### Legal Advice")
1113
+ legal_advice_out = gr.Markdown()
1114
+ advice_tts_out = gr.Audio(label="πŸ”Š Legal Advice Audio", autoplay=False)
1115
+
1116
+ city_dd.change(fn=update_areas, inputs=[city_dd], outputs=[area_dd])
1117
+ submit_btn.click(
1118
+ fn=make_report,
1119
+ inputs=[image_input, issue_type, city_dd, location_tb, name_tb, cnic_tb, phone_tb, desc_tb, language_dd, tts_cb],
1120
+ outputs=[annotated_out, report_out, wa_out, legal_advice_out, report_tts_out, complaint_id_out, advice_tts_out, pdf_download]
1121
+ )
1122
+
1123
+ # TAB 2 β€” KNOW YOUR RIGHTS
1124
+ with gr.Tab("βš–οΈ Know Your Rights"):
1125
+ gr.Markdown("### Civic Laws Quick Reference")
1126
+ with gr.Row():
1127
+ law_issue_dd = gr.Dropdown(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="Issue Type", scale=1)
1128
+ law_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Language", scale=1)
1129
+ law_out = gr.Markdown()
1130
+ gr.Button("πŸ“– Show My Rights", variant="primary").click(fn=law_info, inputs=[law_issue_dd, law_lang_dd], outputs=[law_out])
1131
+
1132
+ gr.HTML("""
1133
+ <div class="info-box">
1134
+ <strong>πŸ“ž Emergency Helplines:</strong><br/>
1135
+ πŸ—‘οΈ Garbage: <span class="hotline-pill">1139</span>
1136
+ πŸ•³οΈ Roads: <span class="hotline-pill">051-9032800</span>
1137
+ πŸ’§ WASA: <span class="hotline-pill">042-99200300</span>
1138
+ πŸ“ž CM Portal: <span class="hotline-pill">0800-02345</span>
1139
+ βš–οΈ Ombudsman: <span class="hotline-pill">051-9204551</span>
1140
+ </div>
1141
+ """)
1142
+
1143
+ gr.Markdown("### Ask a Legal Question")
1144
+ chat_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Response Language")
1145
+ chatbot = gr.Chatbot(label="Legal Assistant", height=400, type="messages")
1146
+
1147
+ with gr.Row():
1148
+ chat_input = gr.Textbox(label="Type your question", placeholder="e.g., WASA didn't fix the pipe for 3 days β€” what can I do?", lines=2, scale=4)
1149
+ chat_send_btn = gr.Button("Send ➀", variant="primary", scale=1)
1150
+
1151
+ gr.Markdown("### 🎀 Voice Question")
1152
+ with gr.Row():
1153
+ chat_audio_in = gr.Audio(type="filepath", label="Record or Upload Question", sources=["microphone", "upload"], scale=3)
1154
+ chat_voice_btn = gr.Button("🎀 Send Voice Question", variant="secondary", scale=1)
1155
+
1156
+ gr.Markdown("### πŸ”Š Listen to Answer")
1157
+ with gr.Row():
1158
+ chat_tts_out = gr.Audio(label="Answer Audio", autoplay=True, scale=3)
1159
+ chat_tts_btn = gr.Button("πŸ”Š Read Last Answer", variant="secondary", scale=1)
1160
+
1161
+ gr.Examples(
1162
+ examples=[
1163
+ ["WASA did not fix the leakage for 3 days. What are my rights?"],
1164
+ ["The water in my area is contaminated. Where do I complain?"],
1165
+ ["Garbage not collected for a week β€” which law applies?"],
1166
+ ["How do I escalate if the authority ignores my complaint?"],
1167
+ ["Pothole damaged my car β€” can I get compensation?"],
1168
+ ],
1169
+ inputs=chat_input,
1170
+ label="πŸ’‘ Common Questions"
1171
+ )
1172
+
1173
+ # Chatbot event handlers
1174
+ chat_send_btn.click(fn=legal_chatbot, inputs=[chat_input, chatbot, chat_lang_dd], outputs=[chatbot, chat_input])
1175
+ chat_input.submit(fn=legal_chatbot, inputs=[chat_input, chatbot, chat_lang_dd], outputs=[chatbot, chat_input])
1176
+ chat_voice_btn.click(fn=voice_to_chatbot, inputs=[chat_audio_in, chatbot, chat_lang_dd], outputs=[chatbot, chat_input])
1177
+ chat_tts_btn.click(fn=read_last_assistant_message, inputs=[chatbot, chat_lang_dd], outputs=[chat_tts_out])
1178
+
1179
+ # TAB 3 β€” VOICE INPUT
1180
+ with gr.Tab("🎀 Voice Input"):
1181
+ gr.Markdown("### Record Your Complaint")
1182
+ gr.HTML('<div class="info-box">πŸŽ™οΈ Record your complaint description. After transcription, copy the text to the complaint form.</div>')
1183
+ audio_in = gr.Audio(type="filepath", label="Record or Upload Audio", sources=["microphone", "upload"])
1184
+ stt_btn = gr.Button("πŸ“ Transcribe to Text", variant="primary")
1185
+ stt_out = gr.Textbox(label="Transcription (editable)", lines=6, interactive=True)
1186
+ stt_btn.click(fn=speech_to_text, inputs=[audio_in], outputs=[stt_out])
1187
+
1188
+ gr.Markdown("### Test Voice Output")
1189
+ with gr.Row():
1190
+ tts_text_in = gr.Textbox(label="Enter text to hear", placeholder="Type something...", scale=3)
1191
+ tts_lang_in = gr.Dropdown(choices=LANGUAGES, value="English", label="Language", scale=1)
1192
+ tts_test_btn = gr.Button("β–Ά Play", variant="secondary")
1193
+ tts_test_out = gr.Audio(label="Audio Output", autoplay=True)
1194
+ tts_test_btn.click(fn=text_to_speech, inputs=[tts_text_in, tts_lang_in], outputs=[tts_test_out])
1195
+
1196
+ # TAB 4 β€” ADMIN
1197
+ with gr.Tab("πŸ›‘οΈ Admin"):
1198
+ gr.Markdown("### Complaint Statistics")
1199
+ refresh_btn = gr.Button("πŸ”„ Refresh", variant="primary")
1200
+ with gr.Row():
1201
+ stats_out = gr.Markdown()
1202
+ log_out = gr.Markdown()
1203
+ refresh_btn.click(fn=get_admin_stats, outputs=[stats_out, log_out])
1204
+
1205
+ return demo
1206
+
1207
+
1208
+ # ─── LAUNCH ───────────────────────────────────────────────────
1209
+ if __name__ == "__main__":
1210
+ print("Rahbar v7.0 starting...")
1211
+ demo = build_ui()
1212
+ demo.launch(
1213
+ server_name="0.0.0.0",
1214
+ server_port=7860,
1215
+ share=True
1216
+ )