Nuzwa commited on
Commit
b7b0db9
·
verified ·
1 Parent(s): 500fa07

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +380 -81
app.py CHANGED
@@ -1,34 +1,38 @@
1
  import os
2
- import json
3
- import time
4
- import gradio as gr
5
  import re
 
6
  from dataclasses import dataclass
7
  from typing import List, Dict, Tuple
8
 
9
- # ---- Language detection ----
 
 
10
  ARABIC_RE = re.compile(r"[\u0600-\u06FF]")
11
  def detect_lang(text: str) -> str:
12
  return "ur" if ARABIC_RE.search(text) else "en"
13
 
14
- # ---- Guardrails ----
 
 
15
  @dataclass
16
  class Guardrails:
17
  refusal_msg_ur: str
18
  refusal_msg_en: str
19
  blocked_patterns: List[str]
20
  soft_patterns: List[str]
 
21
 
22
  @classmethod
23
  def from_yaml(cls, path: str):
24
  import yaml
25
- with open(path, 'r', encoding='utf-8') as f:
26
  data = yaml.safe_load(f)
27
  return cls(
28
- refusal_msg_ur=data['refusal_msg_ur'],
29
- refusal_msg_en=data['refusal_msg_en'],
30
- blocked_patterns=data['blocked_patterns'],
31
- soft_patterns=data['soft_patterns'],
 
32
  )
33
 
34
  def check(self, text: str) -> Tuple[str, str]:
@@ -41,92 +45,387 @@ class Guardrails:
41
  return ("SOFT", p)
42
  return ("OK", "")
43
 
44
- # ---- Tiny placeholder model ----
45
- def tiny_response(user_msg: str, history: List[Tuple[str,str]], lang: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  if lang == "ur":
47
- return "safePak: میں آپ کی رہنمائی کے لیے حاضر ہوں۔ براہِ کرم سوال واضح کریں یا کوئی مثال دیں۔"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  else:
49
- return "safePak: I'm here to help. Please clarify your question or share an example."
50
-
51
- # ---- Toy RAG (in-memory) ----
52
- RAG_DOCS: List[Dict] = []
53
- def add_doc(text: str):
54
- if not text.strip():
55
- return 0
56
- RAG_DOCS.append({"text": text, "lang": detect_lang(text)})
57
- return len(RAG_DOCS)
58
-
59
- def retrieve(query: str, k: int = 3) -> List[str]:
60
- q_tokens = set(re.findall(r"\w+", query.lower()))
61
- scored = []
62
- for d in RAG_DOCS:
63
- d_tokens = set(re.findall(r"\w+", d["text"].lower()))
64
- score = len(q_tokens & d_tokens)
65
- if score:
66
- scored.append((score, d["text"]))
67
- scored.sort(reverse=True, key=lambda x: x[0])
68
- return [t for _, t in scored[:k]]
69
-
70
- # ---- Load guardrails ----
71
- GUARD = Guardrails.from_yaml("guardrails.yaml")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- # ---- Chat logic ----
74
- def chat_fn(user_msg: str, chat_history: List[Tuple[str,str]], use_rag: bool):
75
  if not user_msg:
76
- return chat_history
77
 
78
  lang = detect_lang(user_msg)
79
  status, _ = GUARD.check(user_msg)
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  if status == "BLOCK":
82
- msg = GUARD.refusal_msg_ur if lang=="ur" else GUARD.refusal_msg_en
83
- return chat_history + [(user_msg, msg)]
 
 
 
84
 
85
- context = retrieve(user_msg, k=3) if use_rag else []
86
- reply = tiny_response(user_msg, chat_history, lang)
87
 
88
- if context:
89
- if lang=="ur":
90
- reply += "\n\n(متعلقہ مواد)\n- " + "\n- ".join(context)
91
- else:
92
- reply += "\n\n(Related)\n- " + "\n- ".join(context)
93
 
 
94
  if status == "SOFT":
95
- note = ("احتیاط: آپ کے سوال میں حساس موضوعات شامل ہو سکتے ہیں۔ معلومات عمومی رہنمائی کی حد تک دی جا رہی ہے۔\n\n"
96
- if lang=="ur" else
97
- "Note: Your question may include sensitive topics. Responding with general guidance only.\n\n")
98
- reply = note + reply
99
-
100
- return chat_history + [(user_msg, reply)]
101
-
102
- # ---- Ingest files ----
103
- def ingest_files(files: List[gr.File]):
104
- for f in files or []:
105
- try:
106
- text = open(f.name, 'r', encoding='utf-8', errors='ignore').read()
107
- add_doc(text)
108
- except Exception as e:
109
- print("Failed to read", f.name, e)
110
- return f"Docs in store: {len(RAG_DOCS)}"
111
-
112
- # ---- UI ----
113
- with gr.Blocks(title="safePak Starter", css=".wrap {max-width: 900px; margin: 0 auto}") as demo:
114
- gr.Markdown("# safePak (Starter)\n**لوکل-فرسٹ** ڈیزائن کے ساتھ ایک سادہ ڈیمو۔")
115
-
116
- with gr.Row():
117
- use_rag = gr.Checkbox(label="Use local knowledge (RAG)", value=True)
118
- with gr.Column():
119
- file_uploader = gr.Files(label="Upload .txt files", file_types=[".txt"], file_count="multiple")
120
- ingest_btn = gr.Button("Ingest files")
121
- rag_status = gr.Markdown("Docs in store: 0")
122
-
123
- chatbot = gr.Chatbot(height=420)
124
- msg = gr.Textbox(label="پیغام / Message", placeholder="یہاں لکھیں…", lines=2)
125
  clear = gr.Button("Clear")
126
 
127
- ingest_btn.click(ingest_files, [file_uploader], [rag_status])
128
- msg.submit(lambda u,h,r: (chat_fn(u,h,r), ""), [msg,chatbot,use_rag], [chatbot,msg])
129
- clear.click(lambda: ([], "Docs in store: 0"), [], [chatbot, rag_status])
 
 
 
130
 
131
  if __name__ == "__main__":
132
  demo.launch()
 
1
  import os
 
 
 
2
  import re
3
+ import gradio as gr
4
  from dataclasses import dataclass
5
  from typing import List, Dict, Tuple
6
 
7
+ # ----------------------------
8
+ # Language detection (Urdu/Arabic script vs Latin)
9
+ # ----------------------------
10
  ARABIC_RE = re.compile(r"[\u0600-\u06FF]")
11
  def detect_lang(text: str) -> str:
12
  return "ur" if ARABIC_RE.search(text) else "en"
13
 
14
+ # ----------------------------
15
+ # Guardrails
16
+ # ----------------------------
17
  @dataclass
18
  class Guardrails:
19
  refusal_msg_ur: str
20
  refusal_msg_en: str
21
  blocked_patterns: List[str]
22
  soft_patterns: List[str]
23
+ helplines: List[Dict[str, str]]
24
 
25
  @classmethod
26
  def from_yaml(cls, path: str):
27
  import yaml
28
+ with open(path, "r", encoding="utf-8") as f:
29
  data = yaml.safe_load(f)
30
  return cls(
31
+ refusal_msg_ur=data["refusal_msg_ur"],
32
+ refusal_msg_en=data["refusal_msg_en"],
33
+ blocked_patterns=data["blocked_patterns"],
34
+ soft_patterns=data["soft_patterns"],
35
+ helplines=data.get("helplines", []),
36
  )
37
 
38
  def check(self, text: str) -> Tuple[str, str]:
 
45
  return ("SOFT", p)
46
  return ("OK", "")
47
 
48
+ GUARD = Guardrails.from_yaml("guardrails.yaml")
49
+
50
+ # ----------------------------
51
+ # Small CPU LLM (Qwen2-0.5B-Instruct) with fallback
52
+ # ----------------------------
53
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
54
+ import torch
55
+
56
+ MODEL_ID = os.environ.get("SAFEPak_MODEL_ID", "Qwen/Qwen2-0.5B-Instruct")
57
+ try:
58
+ TOKENIZER = AutoTokenizer.from_pretrained(MODEL_ID)
59
+ MODEL = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32)
60
+ GEN = pipeline(
61
+ "text-generation",
62
+ model=MODEL,
63
+ tokenizer=TOKENIZER,
64
+ device_map="auto" if torch.cuda.is_available() else None,
65
+ )
66
+ TINY_FALLBACK = False
67
+ except Exception as e:
68
+ print("LLM load failed, tiny fallback active:", e)
69
+ TOKENIZER = None
70
+ GEN = None
71
+ TINY_FALLBACK = True
72
+
73
+ def llm_reply(prompt: str, max_new_tokens: int = 160, temperature: float = 0.6) -> str:
74
+ if TINY_FALLBACK:
75
+ return "Stay calm. Take a deep breath. Move to safety now. (fallback)"
76
+ out = GEN(
77
+ prompt,
78
+ max_new_tokens=max_new_tokens,
79
+ do_sample=True,
80
+ temperature=temperature,
81
+ pad_token_id=TOKENIZER.eos_token_id,
82
+ )[0]["generated_text"]
83
+ return out[len(prompt):].strip()
84
+
85
+ # ----------------------------
86
+ # SafePak – 1122 Guide (System Instructions)
87
+ # ----------------------------
88
+ SAFEPAK_SYSTEM = """You are a multilingual emergency disaster response assistant named “SafePak – 1122 Guide”.
89
+ Your role is to provide immediate, complete, to-the-point survival and rescue instructions for users facing any natural disaster, accident, war, or panic scenario in Pakistan or similar regions.
90
+ Behave like a trained emergency coach — calm, practical, never panicked.
91
+
92
+ CORE RULES:
93
+ - Auto-detect language and answer in the user's language (Urdu/English/Punjabi/Sindhi/Pashto/Balochi/Saraiki).
94
+ - Do not ask clarifying questions in normal mode. Assume urgency and provide full, specific, actionable guidance.
95
+ - Use clear bullet points/checklists. Keep responses concise and life-saving.
96
+ - If the user is panicked or crying, begin with a calming line.
97
+ - Trigger relevant response module immediately based on keywords (e.g., fire, flood, earthquake, injury, trapped, explosion).
98
+ - Provide offline-safe advice (assume no electricity/GPS/mobile signal).
99
+ - If the user says “practice with me” or “drill mode,” start Simulation Mode (step-by-step Q&A): ask what they do next, then correct and guide them.
100
+ - Do not store personal data. Do not give medical diagnoses; only emergency first aid.
101
+
102
+ MODULES:
103
+ 1) Risk Identification & Immediate Response
104
+ 2) First Aid & Basic Medical Help
105
+ 3) Fire, Explosion, and Gas Leak Safety
106
+ 4) Earthquake, Flood, Storm, and War Survival
107
+ 5) Terrorism, Bombings, and Conflict Safety
108
+ 6) Evacuation Planning & Go-Bag Checklist
109
+ 7) Mental & Emotional First Aid
110
+ 8) SOPs for Police, Rescue, NGOs
111
+ 9) Home Equipment Safety (Solar, Gas, Water)
112
+ """
113
+
114
+ # ----------------------------
115
+ # Emergency keyword routing (simple templates)
116
+ # ----------------------------
117
+ KW = {
118
+ "fire": ["fire", "آگ", "جل", "burn", "smoke"],
119
+ "gas": ["gas", "لیک", "leak", "بو", "smell gas"],
120
+ "earthquake": ["earthquake", "زلزلہ", "jhatka"],
121
+ "flood": ["flood", "سیلاب", "barish", "overflow"],
122
+ "injury": ["injury", "bleeding", "زخمی", "خون"],
123
+ "explosion": ["explosion", "دھماکا", "blast"],
124
+ "conflict": ["gunfire", "firing", "گولیاں", "terror", "bomb"],
125
+ "trapped": ["trapped", "پھنس", "نکل نہیں"],
126
+ "child": ["child", "بچہ", "رو رہا"],
127
+ "storm": ["storm", "طوفان", "آندھی"],
128
+ }
129
+
130
+ def has_kw(text: str, key: str) -> bool:
131
+ low = text.lower()
132
+ for token in KW.get(key, []):
133
+ if token in low:
134
+ return True
135
+ return False
136
+
137
+ def module_response(user_msg: str, lang: str) -> str:
138
+ bullets = []
139
+ calm_ur = "گھبرائیں نہیں — ایک گہری سانس لیں، میں آپ کے ساتھ ہوں۔"
140
+ calm_en = "Don't worry — take a deep breath; I'm with you."
141
+
142
  if lang == "ur":
143
+ # Fire
144
+ if has_kw(user_msg, "fire"):
145
+ bullets += [
146
+ "فوراً جھک جائیں، دھواں اوپر جاتا ہے۔",
147
+ "ناک اور منہ پر گیلا کپڑا رکھیں۔",
148
+ "بجلی/گیس کے مین سوئچ بند کریں (اگر محفوظ ہو تو).",
149
+ "دروازے کھولنے سے پہلے ہاتھ سے گرمی چیک کریں؛ گرم ہو تو دوسرا راستہ لیں۔",
150
+ "سیڑھیاں استعمال کریں؛ لفٹ ہرگز نہیں۔",
151
+ "بچوں/بزرگوں کو ساتھ رکھیں، حساب رکھیں۔",
152
+ ]
153
+ # Gas Leak
154
+ elif has_kw(user_msg, "gas"):
155
+ bullets += [
156
+ "کسی بھی قسم کی چنگاری نہ بنائیں (سوئچ/لائٹر/فون فلیش نہیں).",
157
+ "کھڑکیاں/دروازے کھول کر ہوا دار کریں۔",
158
+ "گیس مین والو بند کریں۔",
159
+ "عمارت خالی کریں، محفوظ فاصلے پر جائیں۔",
160
+ ]
161
+ # Earthquake
162
+ elif has_kw(user_msg, "earthquake"):
163
+ bullets += [
164
+ "جھکیں، ڈھکیں، اور پکڑیں (Drop, Cover, Hold).",
165
+ "کھڑکیوں، شیشوں اور بھاری الماریوں سے دور رہیں۔",
166
+ "زلزلہ رکتے ہی عمارت کا محفوظ راستہ دیکھ کر باہر جائیں۔",
167
+ "سیڑھیاں استعمال کریں؛ لفٹ نہیں۔",
168
+ ]
169
+ # Flood / Storm
170
+ elif has_kw(user_msg, "flood") or has_kw(user_msg, "storm"):
171
+ bullets += [
172
+ "بجلی مین سوئچ بند کریں (اگر محفوظ ہو تو).",
173
+ "اونچی جگہ پر منتقل ہوں — کرنٹ لگنے کا خطرہ۔",
174
+ "گاڑی کو پانی سے نہ گزاریں؛ 12 انچ پانی بھی خطرناک ہے۔",
175
+ "گو-بیگ، شناختی دستاویزات، دوائیں ساتھ رکھیں۔",
176
+ ]
177
+ # Injury / bleeding
178
+ elif has_kw(user_msg, "injury"):
179
+ bullets += [
180
+ "خون بہہ رہا ہو تو دباؤ ڈال کر روکیں (clean cloth).",
181
+ "زخم کو صاف پانی سے دھوئیں؛ گہرا ہو تو فوری مدد لیں۔",
182
+ "سر/گردن/کمر چوٹ میں مریض کو نہ ہلائیں (spinal risk).",
183
+ ]
184
+ # Explosion / conflict
185
+ elif has_kw(user_msg, "explosion") or has_kw(user_msg, "conflict"):
186
+ bullets += [
187
+ "زمین پر لیٹ جائیں، کور لیں، کھڑکیوں سے دور رہیں۔",
188
+ "فائرنگ کی سمت کے مخالف کور ڈھونڈیں (دیوار/پکی رکاوٹ).",
189
+ "فوراً محفوظ راستہ دیکھ کر نکلیں — رش اور شور سے بچیں۔",
190
+ ]
191
+ # Trapped / child crying
192
+ elif has_kw(user_msg, "trapped") or has_kw(user_msg, "child"):
193
+ bullets += [
194
+ "خاموش اور آہستہ سانس — توانائی بچائیں۔",
195
+ "زور سے کھانسیں/آواز دیں صرف جب مدد قریب لگے۔",
196
+ "ہوا/روشنی کی سمت کا اندازہ رکھیں؛ دھواں ہو تو نیچے رہیں۔",
197
+ ]
198
+ else:
199
+ bullets += [
200
+ "فوراً اپنے آس پاس کے خطرات پہچانیں (بجلی، گیس، دھواں، پانی).",
201
+ "محفوظ جگہ/کور لیں، بچوں/بزرگوں کو ساتھ رکھیں۔",
202
+ "ایگزٹ راستہ ذہن میں رکھیں، لفٹ سے پرہیز کریں۔",
203
+ ]
204
+ lines = ["✅ یہاں آپ کیا کریں:"] + [f"- {b}" for b in bullets]
205
+ lines.append("🕊️ گھبرائیں نہیں — میں آپ کے ساتھ ہوں۔")
206
+ return "\n".join(lines)
207
  else:
208
+ if has_kw(user_msg, "fire"):
209
+ bullets += [
210
+ "Stay low smoke rises.",
211
+ "Cover nose/mouth with a wet cloth.",
212
+ "Turn off electricity/gas if safe.",
213
+ "Check door heat with back of hand; use alternate exit if hot.",
214
+ "Use stairs, never elevators.",
215
+ "Keep children/elderly with you; headcount.",
216
+ ]
217
+ elif has_kw(user_msg, "gas"):
218
+ bullets += [
219
+ "No sparks (no switches/lighters/flash).",
220
+ "Ventilate open windows/doors.",
221
+ "Shut the main gas valve.",
222
+ "Evacuate to a safe distance.",
223
+ ]
224
+ elif has_kw(user_msg, "earthquake"):
225
+ bullets += [
226
+ "Drop, Cover, and Hold On.",
227
+ "Stay away from windows/heavy furniture.",
228
+ "After shaking stops, exit via safe route.",
229
+ "Use stairs; avoid elevators.",
230
+ ]
231
+ elif has_kw(user_msg, "flood") or has_kw(user_msg, "storm"):
232
+ bullets += [
233
+ "Turn off main power (if safe).",
234
+ "Move to higher ground.",
235
+ "Do not drive through water; 12 inches can stall/float cars.",
236
+ "Grab go-bag, IDs, medicines.",
237
+ ]
238
+ elif has_kw(user_msg, "injury"):
239
+ bullets += [
240
+ "Apply direct pressure to stop bleeding.",
241
+ "Rinse wounds with clean water; seek help if deep.",
242
+ "Suspected spine injury — do not move the person.",
243
+ ]
244
+ elif has_kw(user_msg, "explosion") or has_kw(user_msg, "conflict"):
245
+ bullets += [
246
+ "Get low, take cover, away from windows.",
247
+ "Move opposite to gunfire; use solid cover.",
248
+ "Exit safely; avoid crowds and noise.",
249
+ ]
250
+ elif has_kw(user_msg, "trapped") or has_kw(user_msg, "child"):
251
+ bullets += [
252
+ "Stay calm, breathe slowly — conserve energy.",
253
+ "Signal only when rescuers are near.",
254
+ "Stay low if smoke; move toward fresh air/light.",
255
+ ]
256
+ else:
257
+ bullets += [
258
+ "Identify immediate hazards (electricity, gas, smoke, water).",
259
+ "Shelter/cover; keep kids/elderly close.",
260
+ "Know exits; avoid elevators.",
261
+ ]
262
+ lines = ["✅ Do this now:"] + [f"- {b}" for b in bullets]
263
+ lines.append("🕊️ Stay calm — I’m with you.")
264
+ return "\n".join(lines)
265
+
266
+ # ----------------------------
267
+ # Helplines formatter (always added for SOFT/BLOCK)
268
+ # ----------------------------
269
+ def helplines_block(lang: str) -> str:
270
+ if not GUARD.helplines:
271
+ return ""
272
+ hdr = "ہیلپ لائنز:" if lang == "ur" else "Helplines:"
273
+ lines = [hdr]
274
+ for h in GUARD.helplines:
275
+ nm = h.get("name", "").strip()
276
+ ph = h.get("phone", "").strip()
277
+ if nm and ph:
278
+ lines.append(f"{nm}:\n{ph}")
279
+ return "\n".join(lines)
280
+
281
+ # ----------------------------
282
+ # Simulation Mode (Step-by-step Q&A)
283
+ # ----------------------------
284
+ SIM_TRIGGER = re.compile(r"(practice with me|drill mode|drill|پریکٹس|ڈرل)", re.I)
285
+
286
+ def start_sim_scenario(user_msg: str) -> Dict:
287
+ # pick scenario by keyword; default to earthquake
288
+ if has_kw(user_msg, "fire"):
289
+ scen = "fire"
290
+ elif has_kw(user_msg, "flood"):
291
+ scen = "flood"
292
+ elif has_kw(user_msg, "earthquake"):
293
+ scen = "earthquake"
294
+ else:
295
+ scen = "earthquake"
296
+ return {"active": True, "step": 1, "scenario": scen}
297
+
298
+ def sim_prompt(state: Dict, lang: str) -> str:
299
+ scen = state.get("scenario", "earthquake")
300
+ if lang == "ur":
301
+ if state["step"] == 1:
302
+ if scen == "fire":
303
+ return "ڈرل شروع: کمرے میں آگ لگی ہے اور دھواں بڑھ رہا ہے۔ آپ سب سے پہلے کیا کریں گے؟"
304
+ if scen == "flood":
305
+ return "ڈرل شروع: پانی تیزی سے گھر میں داخل ہو رہا ہے۔ آپ سب سے پہلے کیا کریں گے؟"
306
+ return "ڈرل شروع: زلزلے کے جھٹکے محسوس ہو رہے ہیں۔ آپ سب سے پہلے کیا کریں گے؟"
307
+ elif state["step"] == 2:
308
+ return "اچھا—اب دوسرا قدم کیا ہوگا؟ (مختصر ایک لائن میں بتائیں)"
309
+ else:
310
+ return "آخری قدم کیا ہوگا جس سے آپ/گھر والے محفوظ رہیں گے؟"
311
+ else:
312
+ if state["step"] == 1:
313
+ if scen == "fire":
314
+ return "Drill: There is a room fire and smoke is rising. What is your FIRST action?"
315
+ if scen == "flood":
316
+ return "Drill: Water is rapidly entering the house. What is your FIRST action?"
317
+ return "Drill: You feel earthquake shaking. What is your FIRST action?"
318
+ elif state["step"] == 2:
319
+ return "Good — what is your SECOND step? (one short line)"
320
+ else:
321
+ return "What is the FINAL step to keep you/your family safe?"
322
+
323
+ def sim_feedback(state: Dict, user_msg: str, lang: str) -> str:
324
+ scen = state.get("scenario", "earthquake")
325
+ tips_ur = {
326
+ "fire": ["جھک جائیں، دھوئیں سے نیچے رہیں", "گیلا کپڑا ناک/منہ پر", "لفٹ ہرگز نہیں — سیڑھیاں", "گرم دروازہ مت کھولیں"],
327
+ "flood": ["بجلی مین سوئچ بند", "اونچی جگہ/چھت پر جانا", "گاڑی/پانی سے گزرنے سے پرہیز", "گو-بیگ/شناختی دستاویزات ساتھ"],
328
+ "earthquake": ["Drop, Cover, Hold", "کھڑکیوں/بھاری اشیاء سے دور", "جھٹکے رکنے پر سیڑھیوں سے باہر", "لفٹ نہیں"],
329
+ }
330
+ tips_en = {
331
+ "fire": ["Stay low under smoke", "Wet cloth over nose/mouth", "Use stairs, not elevators", "Do not open hot doors"],
332
+ "flood": ["Turn off main power", "Move to higher ground/roof", "Avoid driving through water", "Grab go-bag/IDs"],
333
+ "earthquake": ["Drop, Cover, Hold", "Stay away from windows/heavy objects", "Exit via stairs after shaking", "No elevators"],
334
+ }
335
+ good = tips_ur if lang == "ur" else tips_en
336
+ # very light scoring by keyword overlap
337
+ score = 0
338
+ low = user_msg.lower()
339
+ for hint in good[scen]:
340
+ for tok in re.findall(r"\w+", hint.lower()):
341
+ if tok in low:
342
+ score += 1
343
+ break
344
+ if lang == "ur":
345
+ fb = "درست سمت میں ہیں۔ " if score >= 1 else "بہتر کریں: "
346
+ checklist = "\n- " + "\n- ".join(good[scen])
347
+ return fb + "محفوظ قدم یہ ہیں:" + checklist
348
+ else:
349
+ fb = "Good direction. " if score >= 1 else "Improve: "
350
+ checklist = "\n- " + "\n- ".join(good[scen])
351
+ return fb + "Follow these safe steps:" + checklist
352
+
353
+ # ----------------------------
354
+ # Chat logic
355
+ # ----------------------------
356
+ def build_prompt(user_msg: str, lang: str) -> str:
357
+ return f"<system>\n{SAFEPAK_SYSTEM}\n</system>\n<user>\n{user_msg}\n</user>"
358
 
359
+ def chat_fn(user_msg: str, chat_history: List[Tuple[str, str]], sim_state: Dict):
 
360
  if not user_msg:
361
+ return chat_history, sim_state
362
 
363
  lang = detect_lang(user_msg)
364
  status, _ = GUARD.check(user_msg)
365
 
366
+ # Simulation trigger
367
+ if SIM_TRIGGER.search(user_msg):
368
+ sim_state = start_sim_scenario(user_msg)
369
+ reply = sim_prompt(sim_state, lang)
370
+ return chat_history + [(user_msg, reply)], sim_state
371
+
372
+ # Simulation step flow
373
+ if sim_state.get("active"):
374
+ # give feedback and advance
375
+ feedback = sim_feedback(sim_state, user_msg, lang)
376
+ sim_state["step"] += 1
377
+ if sim_state["step"] > 3:
378
+ sim_state = {"active": False, "step": 0, "scenario": None}
379
+ end_line = "ڈرل مکمل — شاباش! آپ نے بہتر فیصلہ سازی دیکھی۔ محفوظ رہیں۔" if lang == "ur" else "Drill complete — well done. Stay safe."
380
+ return chat_history + [(user_msg, feedback + "\n\n" + end_line)], sim_state
381
+ else:
382
+ prompt_q = sim_prompt(sim_state, lang)
383
+ return chat_history + [(user_msg, feedback + "\n\n" + prompt_q)], sim_state
384
+
385
+ # Normal emergency routing (no questions)
386
  if status == "BLOCK":
387
+ msg = GUARD.refusal_msg_ur if lang == "ur" else GUARD.refusal_msg_en
388
+ helpb = helplines_block(lang)
389
+ if helpb:
390
+ msg += "\n\n" + helpb
391
+ return chat_history + [(user_msg, msg)], sim_state
392
 
393
+ # Immediate checklist from modules
394
+ checklist = module_response(user_msg, lang)
395
 
396
+ # Compose with system prompt for any extra refinement (short)
397
+ prompt = build_prompt(user_msg, lang)
398
+ model_tail = llm_reply(prompt, max_new_tokens=80, temperature=0.5)
 
 
399
 
400
+ # Soft case: prepend a gentle note + add helplines
401
  if status == "SOFT":
402
+ note = "احتیاط: یہ حساس موضوع ہے۔\n\n" if lang == "ur" else "Note: This is a sensitive topic.\n\n"
403
+ checklist = note + checklist
404
+ helpb = helplines_block(lang)
405
+ if helpb:
406
+ checklist += "\n\n" + helpb
407
+
408
+ final = checklist # keep concise; tail is optional if needed
409
+ return chat_history + [(user_msg, final)], sim_state
410
+
411
+ # ----------------------------
412
+ # UI
413
+ # ----------------------------
414
+ with gr.Blocks(title="SafePak – 1122 Guide") as demo:
415
+ gr.Markdown("## SafePak 1122 Guide\nMultilingual emergency assistant (CPU-only).")
416
+
417
+ state = gr.State({"active": False, "step": 0, "scenario": None})
418
+
419
+ chat = gr.Chatbot(height=480)
420
+ msg = gr.Textbox(label="Message / پیغام", placeholder="Type here… / یہاں لکھیں…", lines=2)
 
 
 
 
 
 
 
 
 
 
 
421
  clear = gr.Button("Clear")
422
 
423
+ def on_submit(u, h, s):
424
+ new_h, new_s = chat_fn(u, h, s)
425
+ return new_h, "", new_s
426
+
427
+ msg.submit(on_submit, [msg, chat, state], [chat, msg, state])
428
+ clear.click(lambda: ([], {"active": False, "step": 0, "scenario": None}), [], [chat, state])
429
 
430
  if __name__ == "__main__":
431
  demo.launch()