RozzaCreat commited on
Commit
d25326e
·
verified ·
1 Parent(s): 291da2d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +425 -503
app.py CHANGED
@@ -1,545 +1,467 @@
1
  import gradio as gr
 
2
  import json
3
  import datetime
4
- from smolagents import CodeAgent, DuckDuckGoSearchTool, HfApiModel, tool
5
- from typing import Dict, List, Any
6
  import re
 
 
 
 
 
7
 
8
- # Import your existing tools (assuming they're in the same file or properly imported)
9
- # For this example, I'll include the key functions inline
10
-
11
- @tool
12
- def speech_emotion_analyzer(text: str, context: str = "therapy") -> str:
13
  """
14
- Analyzes patient's emotional state from speech text.
15
-
16
- Args:
17
- text: Patient's speech text
18
- context: Context (therapy, assessment, crisis)
19
-
20
- Returns:
21
- Analysis of emotional state and recommendations
22
  """
23
- if not text or len(text.strip()) < 10:
24
- return "Error: Text must be at least 10 characters long for analysis."
25
-
26
- # Emotion indicators for analysis
27
- emotion_indicators = {
28
- "depression": {
29
- "keywords": ["sad", "hopeless", "tired", "meaningless", "empty", "lonely",
30
- "depressed", "despair", "miserable", "worthless", "numb"],
31
- "patterns": [r"can't.*sleep", r"no.*energy", r"everything.*bad", r"don't.*want",
32
- r"life.*no.*meaning", r"better.*dead"]
33
- },
34
- "anxiety": {
35
- "keywords": ["anxious", "worried", "nervous", "scared", "panic", "tense",
36
- "anxiety", "worry", "fear", "stress", "phobia"],
37
- "patterns": [r"what.*if", r"afraid.*that", r"can't.*cope", r"heart.*racing",
38
- r"hands.*shaking", r"can't.*breathe", r"panic.*attack"]
39
- },
40
- "anger": {
41
- "keywords": ["angry", "furious", "irritated", "annoyed", "hate", "mad",
42
- "rage", "aggression", "hatred", "frustration"],
43
- "patterns": [r"fed up", r"sick of", r"can't.*stand", r"want.*hit",
44
- r"everything.*irritates", r"pissed off"]
45
- },
46
- "stress": {
47
- "keywords": ["stress", "pressure", "overwhelmed", "exhausted", "burned out",
48
- "burnout", "fatigue","job interview", "drained"],
49
- "patterns": [r"too.*much.*work", r"can't.*keep up", r"headache", r"no.*time",
50
- r"swamped", r"working.*nonstop",r"job.*interview"]
51
- },
52
- "trauma": {
53
- "keywords": ["trauma", "flashbacks", "nightmares", "memories", "avoiding",
54
- "ptsd", "triggered", "shock"],
55
- "patterns": [r"keep.*remembering", r"bad.*dreams", r"can't.*forget",
56
- r"avoid.*places", r"triggers"]
57
- }
58
- }
59
-
60
- # Analyze text
61
- text_lower = text.lower()
62
- detected_emotions = {}
63
 
64
- for emotion, indicators in emotion_indicators.items():
65
- score = 0
66
- matches = []
 
67
 
68
- # Check keywords
69
- for keyword in indicators["keywords"]:
70
- if keyword in text_lower:
71
- score += 1
72
- matches.append(keyword)
73
-
74
- # Check patterns
75
- for pattern in indicators["patterns"]:
76
- if re.search(pattern, text_lower):
77
- score += 2
78
- matches.append(f"pattern: {pattern}")
79
-
80
- if score > 0:
81
- detected_emotions[emotion] = {"score": score, "matches": matches}
82
-
83
- # Check for crisis indicators
84
- crisis_indicators = ["suicide", "kill myself", "end it all", "not worth living", "better off dead"]
85
- crisis_risk = any(indicator in text_lower for indicator in crisis_indicators)
86
-
87
- # Check positive indicators
88
- positive_indicators = ["happy", "joy", "good", "great", "wonderful", "excited", "grateful", "love"]
89
- positive_count = sum(1 for indicator in positive_indicators if indicator in text_lower)
90
-
91
- # Create report
92
- report = "## 🧠 Emotional State Analysis\n\n"
93
-
94
- if detected_emotions:
95
- report += "### 📊 Detected Emotional States:\n"
96
- for emotion, data in sorted(detected_emotions.items(), key=lambda x: x[1]["score"], reverse=True):
97
- emotion_names = {
98
- "depression": "😔 Depression signs",
99
- "anxiety": "😰 Anxiety",
100
- "anger": "😠 Anger/Irritation",
101
- "stress": "😵 Stress",
102
- "trauma": "💔 Trauma-related"
 
103
  }
104
- intensity = "High" if data['score'] >= 4 else "Medium" if data['score'] >= 2 else "Low"
105
- report += f"- {emotion_names[emotion]} (intensity: {intensity})\n"
106
- report += f" Found indicators: {', '.join(data['matches'][:3])}\n\n"
107
- else:
108
- report += "✅ No clear signs of emotional distress detected.\n\n"
109
-
110
- # Positive aspects
111
- if positive_count > 0:
112
- report += f"😊 Positive indicators: Found {positive_count} positive marker(s)\n\n"
113
-
114
- # Crisis assessment
115
- if crisis_risk:
116
- report += "🚨 CRITICAL: Suicide risk indicators detected!\n"
117
- report += "📞 Immediate consultation and safety assessment recommended.\n"
118
- report += "Emergency services: 101, Crisis hotline: \n\n"
119
-
120
- # Treatment recommendations
121
- report += "### 🎯 Treatment Recommendations:\n"
122
-
123
- if "depression" in detected_emotions:
124
- report += "- 🧘 CBT (Cognitive Behavioral Therapy) for negative thought patterns\n"
125
- report += "- 💊 Consider psychiatric consultation for medication support\n"
126
- report += "- 🏃 Behavioral activation (increase pleasant activities)\n"
127
-
128
- if "anxiety" in detected_emotions:
129
- report += "- 🫁 Relaxation techniques: breathing exercises, progressive muscle relaxation\n"
130
- report += "- 🎭 Exposure therapy for phobic avoidance\n"
131
- report += "- 🧠 Mindfulness therapy for anxious thoughts\n"
132
-
133
- if "anger" in detected_emotions:
134
- report += "- 🎯 Anger management training and self-regulation techniques\n"
135
- report += "- 🔍 Trigger identification and behavior patterns\n"
136
- report += "- 🤝 Communication skills training\n"
137
-
138
- if "stress" in detected_emotions:
139
- report += "- ⏰ Stress management and time management techniques\n"
140
- report += "- ⚖️ Work-life balance assessment and adjustment\n"
141
- report += "- 🛡️ Coping strategies for stressful situations\n"
142
-
143
- if "trauma" in detected_emotions:
144
- report += "- 👁️ EMDR therapy for traumatic memories\n"
145
- report += "- 🏠 Trauma-informed approach in therapy\n"
146
- report += "- 🧘 Somatic techniques for body-based trauma symptoms\n"
147
-
148
- if not detected_emotions or positive_count > 2:
149
- report += "- 💪 Supportive therapy to maintain resources\n"
150
- report += "- 🎯 Personal growth and life goals development\n"
151
-
152
- report += f"\n📅 Analysis date: {datetime.datetime.now().strftime('%m/%d/%Y %H:%M')}"
153
- report += f"\n🌍 Context: {context}"
154
-
155
- return report
156
-
157
- @tool
158
- def create_therapy_worksheet(therapy_type: str, client_age: int, issue: str) -> str:
159
- """
160
- Creates a therapeutic worksheet/exercise for client.
161
-
162
- Args:
163
- therapy_type: Type of therapy (cbt, dbt, art, mindfulness)
164
- client_age: Client's age
165
- issue: Main problem
166
-
167
- Returns:
168
- Structured therapeutic worksheet
169
- """
170
- if client_age < 5 or client_age > 100:
171
- return "Error: Age must be between 5 and 100."
172
-
173
- therapy_type = therapy_type.lower()
174
- valid_types = ["cbt", "dbt", "art", "mindfulness"]
175
-
176
- if therapy_type not in valid_types:
177
- return f"Error: Therapy type must be one of: {', '.join(valid_types)}"
178
-
179
- # Determine level by age
180
- if client_age < 12:
181
- level = "child"
182
- elif client_age < 18:
183
- level = "teen"
184
- else:
185
- level = "adult"
186
 
187
- worksheets = {
188
- "cbt": {
189
- "child": {
190
- "title": "🌈 My Thoughts and Feelings",
191
- "exercises": [
192
- "🌤️ Mood Diary: Draw or mark your mood with an emoji each day",
193
- "🤔 Thought Catcher: Write down thoughts when sad or scared",
194
- "🎯 Helper Thoughts: Find 3 good thoughts for each bad thought",
195
- "⭐ My Successes: Write one good thing you did each day"
196
- ]
197
- },
198
- "teen": {
199
- "title": "🧠 Cognitive Restructuring",
200
- "exercises": [
201
- "📊 ABC Model: Situation (A) → Thought (B) → Feeling/Behavior (C)",
202
- "🔍 Thought Detective: Find evidence FOR and AGAINST your beliefs",
203
- "⚖️ Alternative Thoughts: Find 2-3 realistic alternatives for negative thoughts",
204
- "📝 Thought Diary: Daily record of automatic thoughts"
205
- ]
206
- },
207
- "adult": {
208
- "title": "🔄 Cognitive Behavioral Therapy",
209
- "exercises": [
210
- "🔄 Thought-Feeling-Behavior Cycle: Analyze connections in situations",
211
- "⚡ Cognitive Distortions: Identify and correct thinking errors",
212
- "🎯 Behavioral Experiments: Test catastrophic beliefs",
213
- "📈 Graded Tasks: Step-by-step overcoming avoidance"
214
- ]
215
- }
216
- },
217
- "dbt": {
218
- "teen": {
219
- "title": "🌊 Emotion Regulation Skills",
220
- "exercises": [
221
- "🌊 TIPP: Temperature, Intense exercise, Paced breathing, Paired muscle relaxation",
222
- "🚦 STOP: Stop → Take a breath → Observe → Proceed mindfully",
223
- "🎭 Emotion Diary: Track emotion intensity (1-10) with triggers",
224
- "🧘 Mindfulness: 5-10 minutes daily breathing meditation"
225
- ]
226
- },
227
- "adult": {
228
- "title": "⚖️ Dialectical Behavior Therapy",
229
- "exercises": [
230
- "⚖️ Wise Mind: Balance emotional and rational mind",
231
- "🛡️ Crisis Skills: TIPP, distraction, self-soothing",
232
- "🤝 DEAR MAN: Technique for healthy communication",
233
- "🎯 Radical Acceptance: Practice accepting unchangeable reality"
234
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
- },
237
- "mindfulness": {
238
- "child": {
239
- "title": "🐸 Mindfulness for Kids",
240
- "exercises": [
241
- "🐸 Frog Pose: Sit still like a frog and listen to sounds",
242
- "🌬️ Belly Breathing: Watch a toy rise and fall on your belly",
243
- "👀 5-4-3-2-1: Find 5 things you see, 4 you hear, 3 you can touch...",
244
- "🍯 Slow Eating: Eat one raisin very slowly and mindfully"
245
- ]
246
- },
247
- "teen": {
248
- "title": "🧘 Teen Mindfulness",
249
- "exercises": [
250
- "📱 Digital Detox: 30-60 minutes without devices",
251
- "🚶 Mindful Walking: Walk slowly noticing each step",
252
- "💭 Thought Watching: Watch thoughts like clouds passing",
253
- "❤️ Body Scan: Relax and notice each body part"
254
- ]
255
- },
256
- "adult": {
257
- "title": "🧘‍♀️ Mindfulness Practice",
258
- "exercises": [
259
- "🧘 Formal Meditation: 20-30 minutes daily sitting practice",
260
- "🍽️ Mindful Eating: Full attention to taste, smell, texture",
261
- "💼 Work Mindfulness: Regular pauses to return to present",
262
- "😴 Sleep Meditation: Body scan and breathing for better sleep"
263
- ]
264
  }
265
- }
266
- }
267
-
268
- # Get appropriate exercises
269
- therapy_data = worksheets.get(therapy_type, {})
270
- level_data = therapy_data.get(level, therapy_data.get("adult", {}))
271
 
272
- if not level_data:
273
- return f"Exercises for {therapy_type} therapy and age {client_age} not yet available."
274
-
275
- # Create worksheet
276
- worksheet = f"# {level_data['title']}\n\n"
277
- worksheet += f"**👤 Client**: {client_age} years old\n"
278
- worksheet += f"**🎯 Main issue**: {issue}\n"
279
- worksheet += f"**🔬 Therapy type**: {therapy_type.upper()}\n"
280
- worksheet += f"**📅 Date created**: {datetime.datetime.now().strftime('%m/%d/%Y')}\n\n"
281
-
282
- worksheet += "## 📋 Exercises:\n\n"
283
-
284
- for i, exercise in enumerate(level_data['exercises'], 1):
285
- worksheet += f"### {i}. {exercise}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
- # Add space for notes
288
- worksheet += "**📝 Notes:**\n"
289
- worksheet += "```\n"
290
- worksheet += "Date completed: ___________\n"
291
- worksheet += "Observations: \n\n\n"
292
- worksheet += "Emotions during exercise: \n\n"
293
- worksheet += "Insights: \n\n"
294
- worksheet += "```\n\n"
295
-
296
- worksheet += "---\n\n"
297
- worksheet += "## 📖 Instructions:\n"
298
- worksheet += "✅ Complete exercises at your own pace\n"
299
- worksheet += "✅ Record all observations - this is important for progress\n"
300
- worksheet += "✅ Discuss results with your therapist\n"
301
- worksheet += "✅ Stop if you feel too uncomfortable and seek support\n\n"
302
 
303
- worksheet += "## 🆘 When to get help:\n"
304
- worksheet += "⚠️ If exercises cause severe anxiety or panic\n"
305
- worksheet += "⚠️ If you have thoughts of self-harm\n"
306
- worksheet += "⚠️ If symptoms worsen\n\n"
307
-
308
- worksheet += "📞 Emergency contacts:\n"
309
- worksheet += "- Crisis hotline: 988\n"
310
- worksheet += "- Emergency: 101\n"
311
- worksheet += "- Your therapist: ________________\n\n"
312
-
313
- return worksheet
314
-
315
- def mood_tracker_analyzer(mood_data_str: str) -> str:
316
- """
317
- Analyzes mood tracking data to identify patterns.
318
- """
319
- try:
320
- # Try to parse as JSON
321
- data = json.loads(mood_data_str)
322
- except:
323
- # If not JSON, try to parse simple format like "5,7,3,8,6"
324
  try:
325
- moods = [float(x.strip()) for x in mood_data_str.split(',')]
326
- data = [{"mood": mood, "date": f"Day {i+1}"} for i, mood in enumerate(moods)]
327
- except:
328
- return "Error: Please provide mood data as JSON or comma-separated numbers (e.g., '5,7,3,8,6')"
329
-
330
- if not isinstance(data, list) or len(data) == 0:
331
- return "Error: Mood data should be a non-empty list of entries."
332
-
333
- # Analyze mood patterns
334
- moods = []
335
- dates = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
- for entry in data:
338
- if isinstance(entry, dict) and 'mood' in entry:
339
- moods.append(float(entry['mood']))
340
- dates.append(entry.get('date', f'Entry {len(dates)+1}'))
341
- elif isinstance(entry, (int, float)):
342
- moods.append(float(entry))
343
- dates.append(f'Entry {len(dates)+1}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
- if not moods:
346
- return "Error: No valid mood entries found."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
- # Calculate statistics
349
- avg_mood = sum(moods) / len(moods)
350
- mood_trend = "stable"
351
 
352
- if len(moods) > 3:
353
- recent_avg = sum(moods[-3:]) / 3
354
- older_avg = sum(moods[:-3]) / (len(moods) - 3)
355
 
356
- if recent_avg > older_avg + 0.5:
357
- mood_trend = "improving"
358
- elif recent_avg < older_avg - 0.5:
359
- mood_trend = "declining"
360
-
361
- # Create report
362
- report = "# 📊 Mood Tracking Analysis\n\n"
363
- report += f"**Period**: {dates[0]} to {dates[-1]}\n"
364
- report += f"**Entries analyzed**: {len(moods)}\n"
365
- report += f"**Average mood**: {avg_mood:.1f}/10\n"
366
- report += f"**Trend**: {mood_trend}\n\n"
367
-
368
- report += "## 📈 Pattern Analysis:\n"
369
-
370
- # Check for low mood periods
371
- low_mood_days = sum(1 for m in moods if m < 5)
372
- if low_mood_days > len(moods) * 0.3:
373
- report += "⚠️ Concern: Low mood detected in >30% of entries\n"
374
- report += " → Consider professional evaluation for depression\n\n"
375
-
376
- # Check volatility
377
- if len(moods) > 2:
378
- volatility = sum(abs(moods[i] - moods[i-1]) for i in range(1, len(moods))) / (len(moods) - 1)
379
- if volatility > 2:
380
- report += "🎢 High mood volatility detected\n"
381
- report += " → May benefit from emotion regulation skills\n\n"
382
-
383
- report += "## 💡 Recommendations:\n"
384
-
385
- if mood_trend == "declining":
386
- report += "- 🚨 Schedule appointment with mental health professional\n"
387
- report += "- 🧘 Increase self-care activities\n"
388
- report += "- 👥 Reach out to support network\n"
389
- elif mood_trend == "improving":
390
- report += "- ✅ Continue current strategies\n"
391
- report += "- 📝 Document what's working well\n"
392
- report += "- 🎯 Set new wellness goals\n"
393
- else:
394
- report += "- 📊 Continue tracking for more data\n"
395
- report += "- 🔍 Look for mood triggers\n"
396
- report += "- 💪 Maintain healthy routines\n"
397
-
398
- report += f"\n*Analysis completed: {datetime.datetime.now().strftime('%m/%d/%Y %H:%M')}*"
399
-
400
- return report
401
-
402
- # Create the Gradio interface
403
- def create_interface():
404
- """Create and return the Gradio interface"""
405
-
406
- with gr.Blocks(title="🧠 Mental Health Therapy Assistant", theme=gr.themes.Soft()) as app:
407
  gr.Markdown("""
408
- # 🧠 Mental Health Therapy Assistant
 
 
409
 
410
- **⚠️ Important Disclaimer**: This tool is for educational and supportive purposes only.
411
- It is not a substitute for professional mental health care. If you're in crisis,
412
- please contact emergency services (911) or the crisis hotline (988).
413
  """)
414
 
415
- with gr.Tabs():
416
- # Emotion Analysis Tab
417
- with gr.TabItem("🎭 Emotion Analysis"):
418
- gr.Markdown("### Analyze emotional content from speech or text")
419
 
420
- with gr.Row():
421
- with gr.Column():
422
- emotion_text = gr.Textbox(
423
- label="Patient Speech/Text",
424
- placeholder="Enter the text you want to analyze for emotional content...",
425
- lines=5
426
- )
427
- emotion_context = gr.Dropdown(
428
- choices=["therapy", "assessment", "crisis"],
429
- value="therapy",
430
- label="Context"
431
- )
432
- emotion_btn = gr.Button("🔍 Analyze Emotions", variant="primary")
433
-
434
- with gr.Column():
435
- emotion_output = gr.Markdown(label="Analysis Results")
436
-
437
- emotion_btn.click(
438
- fn=speech_emotion_analyzer,
439
- inputs=[emotion_text, emotion_context],
440
- outputs=emotion_output
441
  )
442
-
443
- # Worksheet Generator Tab
444
- with gr.TabItem("📝 Therapy Worksheets"):
445
- gr.Markdown("### Generate age-appropriate therapeutic exercises")
446
 
447
- with gr.Row():
448
- with gr.Column():
449
- worksheet_therapy = gr.Dropdown(
450
- choices=["CBT", "DBT", "Art", "Mindfulness"],
451
- value="CBT",
452
- label="Therapy Type"
453
- )
454
- worksheet_age = gr.Slider(
455
- minimum=5,
456
- maximum=100,
457
- value=25,
458
- step=1,
459
- label="Client Age"
460
- )
461
- worksheet_issue = gr.Textbox(
462
- label="Main Issue/Concern",
463
- placeholder="e.g., anxiety, depression, anger management..."
464
- )
465
- worksheet_btn = gr.Button("📋 Generate Worksheet", variant="primary")
466
-
467
- with gr.Column():
468
- worksheet_output = gr.Markdown(label="Generated Worksheet")
469
-
470
- worksheet_btn.click(
471
- fn=create_therapy_worksheet,
472
- inputs=[worksheet_therapy, worksheet_age, worksheet_issue],
473
- outputs=worksheet_output
474
  )
475
-
476
- # Mood Tracker Tab
477
- with gr.TabItem("📊 Mood Analysis"):
478
- gr.Markdown("### Analyze mood tracking patterns")
479
-
480
- with gr.Row():
481
- with gr.Column():
482
- mood_data = gr.Textbox(
483
- label="Mood Data",
484
- placeholder="Enter mood scores (1-10) separated by commas: 5,7,3,8,6\nOr JSON format: [{\"mood\": 5, \"date\": \"2024-01-01\"}, ...]",
485
- lines=5
486
- )
487
- mood_btn = gr.Button("📈 Analyze Mood Patterns", variant="primary")
488
-
489
- gr.Markdown("""
490
- **Example formats:**
491
- - Simple: `5,7,3,8,6,4,9`
492
- - JSON: `[{"mood": 5, "date": "2024-01-01"}, {"mood": 7, "date": "2024-01-02"}]`
493
- """)
494
-
495
- with gr.Column():
496
- mood_output = gr.Markdown(label="Mood Analysis Results")
497
 
498
- mood_btn.click(
499
- fn=mood_tracker_analyzer,
500
- inputs=mood_data,
501
- outputs=mood_output
502
  )
503
-
504
- # Resources Tab
505
- with gr.TabItem("📚 Resources"):
506
- gr.Markdown("""
507
- ### 🆘 Crisis Resources
508
-
509
- **Immediate Help:**
510
- - 🚨 Emergency: **911**
511
- - 📞 Crisis Hotline: **988** (Suicide & Crisis Lifeline)
512
- - 💬 Crisis Text Line: Text HOME to **741741**
513
 
514
- **Online Resources:**
515
- - [National Alliance on Mental Illness (NAMI)](https://nami.org)
516
- - [Mental Health America](https://www.mhanational.org)
517
- - [Psychology Today Therapist Finder](https://www.psychologytoday.com)
518
-
519
- ### 🧘 Self-Care Tips
520
-
521
- **Daily Practices:**
522
- - 🌅 Maintain regular sleep schedule
523
- - 🏃 Regular physical activity
524
- - 🥗 Balanced nutrition
525
- - 🧘 Mindfulness/meditation practice
526
- - 👥 Social connections
527
- - 📚 Limit news/social media if overwhelming
528
-
529
- **Remember:** This tool provides educational support but cannot replace professional mental health care.
530
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
 
532
  return app
533
 
534
  # Main execution
535
  if __name__ == "__main__":
536
- # Create and launch the interface
537
- app = create_interface()
538
 
539
- # Launch with appropriate settings for deployment
540
  app.launch(
541
- server_name="0.0.0.0", # Allow external access
542
- server_port=7860, # Standard port for Hugging Face Spaces
543
- share=False, # Don't create a public link
544
- debug=False # Disable debug mode for production
 
545
  )
 
1
  import gradio as gr
2
+ import requests
3
  import json
4
  import datetime
 
 
5
  import re
6
+ from bs4 import BeautifulSoup
7
+ from typing import Optional, Dict, Any
8
+ import geocoder
9
+ import pytz
10
+ from urllib.parse import quote, urljoin
11
 
12
+ class ShabbatCandleLightingAgent:
 
 
 
 
13
  """
14
+ AI Agent for finding Shabbat candle lighting times worldwide using Chabad.org
 
 
 
 
 
 
 
15
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ def __init__(self):
18
+ self.base_url = "https://www.chabad.org"
19
+ self.candle_lighting_url = "https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm"
20
+ self.search_url = "https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm"
21
 
22
+ def get_user_location(self) -> Optional[Dict[str, Any]]:
23
+ """
24
+ Automatically detect user's location using IP geolocation
25
+ """
26
+ try:
27
+ # Get location from IP
28
+ g = geocoder.ip('me')
29
+ if g.ok:
30
+ return {
31
+ 'city': g.city,
32
+ 'country': g.country,
33
+ 'lat': g.latlng[0] if g.latlng else None,
34
+ 'lng': g.latlng[1] if g.latlng else None,
35
+ 'timezone': g.timezone if hasattr(g, 'timezone') else None
36
+ }
37
+ except Exception as e:
38
+ print(f"Location detection error: {e}")
39
+ return None
40
+
41
+ def search_city_on_chabad(self, city_name: str) -> Optional[str]:
42
+ """
43
+ Search for a city on Chabad.org and get the specific URL for candle lighting times
44
+ """
45
+ try:
46
+ # Format city name for search
47
+ search_query = quote(city_name)
48
+
49
+ # Try different search approaches
50
+ search_urls = [
51
+ f"https://www.chabad.org/calendar/candlelighting_cdo/city/{search_query}/jewish/Candle-Lighting.htm",
52
+ f"https://www.chabad.org/calendar/candlelighting_cdo/jewish/Candle-Lighting.htm?city={search_query}",
53
+ f"https://www.chabad.org/calendar/candlelighting.asp?city={search_query}"
54
+ ]
55
+
56
+ headers = {
57
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
58
  }
59
+
60
+ for url in search_urls:
61
+ try:
62
+ response = requests.get(url, headers=headers, timeout=10)
63
+ if response.status_code == 200 and 'candle lighting' in response.text.lower():
64
+ return url
65
+ except:
66
+ continue
67
+
68
+ # Try the main search page with POST request
69
+ try:
70
+ search_payload = {
71
+ 'city': city_name,
72
+ 'submit': 'Search'
73
+ }
74
+ response = requests.post(self.candle_lighting_url, data=search_payload, headers=headers, timeout=10)
75
+ if response.status_code == 200:
76
+ return response.url
77
+ except:
78
+ pass
79
+
80
+ except Exception as e:
81
+ print(f"City search error: {e}")
82
+
83
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ def extract_candle_lighting_times(self, html_content: str, city_name: str) -> Dict[str, Any]:
86
+ """
87
+ Extract candle lighting times from Chabad.org HTML content
88
+ """
89
+ try:
90
+ soup = BeautifulSoup(html_content, 'html.parser')
91
+
92
+ # Look for candle lighting time patterns
93
+ time_patterns = [
94
+ r'candle lighting[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)',
95
+ r'light candles[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)',
96
+ r'friday[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)',
97
+ r'(\d{1,2}:\d{2}\s*[ap]m?).*candle',
98
+ r'(\d{1,2}:\d{2}\s*[ap]m?).*light'
99
+ ]
100
+
101
+ # Look for Havdalah time patterns
102
+ havdalah_patterns = [
103
+ r'havdalah[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)',
104
+ r'saturday[:\s]*(\d{1,2}:\d{2}(?:\s*[ap]m)?)',
105
+ r'(\d{1,2}:\d{2}\s*[ap]m?).*havdalah'
106
+ ]
107
+
108
+ text = soup.get_text().lower()
109
+
110
+ # Find candle lighting time
111
+ candle_time = None
112
+ for pattern in time_patterns:
113
+ match = re.search(pattern, text, re.IGNORECASE)
114
+ if match:
115
+ candle_time = match.group(1)
116
+ break
117
+
118
+ # Find Havdalah time
119
+ havdalah_time = None
120
+ for pattern in havdalah_patterns:
121
+ match = re.search(pattern, text, re.IGNORECASE)
122
+ if match:
123
+ havdalah_time = match.group(1)
124
+ break
125
+
126
+ # Look for specific elements with common class names/IDs
127
+ if not candle_time:
128
+ time_elements = soup.find_all(['span', 'div', 'td'],
129
+ class_=re.compile(r'time|candle|light', re.I))
130
+ for element in time_elements:
131
+ time_text = element.get_text()
132
+ time_match = re.search(r'\d{1,2}:\d{2}(?:\s*[ap]m)?', time_text)
133
+ if time_match and not candle_time:
134
+ candle_time = time_match.group()
135
+
136
+ # Get current date info
137
+ now = datetime.datetime.now()
138
+ friday = now + datetime.timedelta(days=(4 - now.weekday()) % 7)
139
+ saturday = friday + datetime.timedelta(days=1)
140
+
141
+ return {
142
+ 'city': city_name,
143
+ 'candle_lighting_time': candle_time,
144
+ 'havdalah_time': havdalah_time,
145
+ 'friday_date': friday.strftime('%B %d, %Y'),
146
+ 'saturday_date': saturday.strftime('%B %d, %Y'),
147
+ 'found_data': bool(candle_time or havdalah_time),
148
+ 'raw_text_sample': text[:500] if text else ''
149
  }
150
+
151
+ except Exception as e:
152
+ print(f"Time extraction error: {e}")
153
+ return {
154
+ 'city': city_name,
155
+ 'candle_lighting_time': None,
156
+ 'havdalah_time': None,
157
+ 'friday_date': None,
158
+ 'saturday_date': None,
159
+ 'found_data': False,
160
+ 'error': str(e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  }
 
 
 
 
 
 
162
 
163
+ def get_alternative_times(self, city_name: str) -> Optional[Dict[str, Any]]:
164
+ """
165
+ Try alternative sources for Shabbat times
166
+ """
167
+ try:
168
+ # Try MyZmanim API (if available)
169
+ api_urls = [
170
+ f"https://www.myzmanim.com/api.aspx?mode=shabbat&city={quote(city_name)}",
171
+ f"https://www.hebcal.com/shabbat?city={quote(city_name)}&M=on"
172
+ ]
173
+
174
+ headers = {
175
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
176
+ }
177
+
178
+ for url in api_urls:
179
+ try:
180
+ response = requests.get(url, headers=headers, timeout=10)
181
+ if response.status_code == 200:
182
+ # Try to parse response
183
+ if 'json' in response.headers.get('content-type', ''):
184
+ data = response.json()
185
+ return data
186
+ else:
187
+ # Parse HTML response
188
+ soup = BeautifulSoup(response.text, 'html.parser')
189
+ times = self.extract_candle_lighting_times(response.text, city_name)
190
+ if times['found_data']:
191
+ return times
192
+ except:
193
+ continue
194
+
195
+ except Exception as e:
196
+ print(f"Alternative source error: {e}")
197
 
198
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
+ def get_shabbat_times(self, city_input: str = "", auto_detect: bool = True) -> str:
201
+ """
202
+ Main function to get Shabbat candle lighting times
203
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  try:
205
+ city_name = city_input.strip()
206
+
207
+ # Auto-detect location if requested and no city provided
208
+ if auto_detect and not city_name:
209
+ location = self.get_user_location()
210
+ if location and location.get('city'):
211
+ city_name = location['city']
212
+ if location.get('country'):
213
+ city_name += f", {location['country']}"
214
+
215
+ if not city_name:
216
+ return self._format_error_response("❌ Please provide a city name or enable location detection.")
217
+
218
+ # Search for the city on Chabad.org
219
+ city_url = self.search_city_on_chabad(city_name)
220
+
221
+ result_data = None
222
+ source = "Chabad.org"
223
+
224
+ if city_url:
225
+ try:
226
+ headers = {
227
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
228
+ }
229
+ response = requests.get(city_url, headers=headers, timeout=15)
230
+
231
+ if response.status_code == 200:
232
+ result_data = self.extract_candle_lighting_times(response.text, city_name)
233
+
234
+ except Exception as e:
235
+ print(f"Chabad.org fetch error: {e}")
236
+
237
+ # Try alternative sources if Chabad.org didn't work
238
+ if not result_data or not result_data.get('found_data'):
239
+ alternative_data = self.get_alternative_times(city_name)
240
+ if alternative_data:
241
+ result_data = alternative_data
242
+ source = "Alternative source"
243
+
244
+ # Format and return the response
245
+ if result_data and result_data.get('found_data'):
246
+ return self._format_success_response(result_data, source, city_url)
247
+ else:
248
+ return self._format_not_found_response(city_name)
249
+
250
+ except Exception as e:
251
+ return self._format_error_response(f"❌ An error occurred: {str(e)}")
252
 
253
+ def _format_success_response(self, data: Dict[str, Any], source: str, url: str = None) -> str:
254
+ """Format successful response with Shabbat times"""
255
+ response = f"""
256
+ # 🕯️ Shabbat Candle Lighting Times
257
+
258
+ ## 📍 Location: {data.get('city', 'Unknown')}
259
+
260
+ ### 🕯️ **Candle Lighting (Friday)**
261
+ **Time:** {data.get('candle_lighting_time', 'Not found')}
262
+ **Date:** {data.get('friday_date', 'This Friday')}
263
+
264
+ ### ⭐ **Havdalah (Saturday)**
265
+ **Time:** {data.get('havdalah_time', 'Not found')}
266
+ **Date:** {data.get('saturday_date', 'This Saturday')}
267
+
268
+ ---
269
+
270
+ ## 📋 **Important Notes:**
271
+ - 🕯️ Light Shabbat candles **18 minutes before sunset** on Friday
272
+ - ⭐ Havdalah is performed after **3 stars appear** on Saturday night
273
+ - 🌍 Times are specific to your geographic location
274
+ - ⏰ Always verify times with your local rabbi or Jewish community
275
+
276
+ ## 📚 **Source Information:**
277
+ **Data from:** {source}
278
+ **Retrieved:** {datetime.datetime.now().strftime('%B %d, %Y at %I:%M %p')}
279
+ {f"**URL:** {url}" if url else ""}
280
+
281
+ ---
282
+
283
+ ## 🙏 **Shabbat Shalom!**
284
+ *May your Shabbat be filled with peace, joy, and spiritual connection.*
285
+ """
286
+ return response
287
+
288
+ def _format_not_found_response(self, city_name: str) -> str:
289
+ """Format response when times are not found"""
290
+ return f"""
291
+ # 🕯️ Shabbat Candle Lighting Times
292
+
293
+ ## ❌ Times Not Found
294
+
295
+ Unfortunately, we couldn't find specific Shabbat candle lighting times for **{city_name}**.
296
+
297
+ ### 🔍 **What you can try:**
298
+ 1. **Check spelling** - Make sure the city name is spelled correctly
299
+ 2. **Add country** - Try "City, Country" format (e.g., "Paris, France")
300
+ 3. **Use major city** - Try the nearest major city instead
301
+ 4. **Manual lookup** - Visit [Chabad.org](https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm) directly
302
+
303
+ ### 📍 **General Guidelines:**
304
+ - 🕯️ **Candle lighting:** Typically 18 minutes before sunset on Friday
305
+ - ⭐ **Havdalah:** Approximately 25-42 minutes after sunset on Saturday
306
+ - 🌍 **Location matters:** Times vary significantly by geographic location
307
+
308
+ ### 📞 **Local Resources:**
309
+ - Contact your local Chabad house or synagogue
310
+ - Check with your Jewish community center
311
+ - Consult your rabbi for precise local customs
312
+
313
+ ---
314
+
315
+ **Retrieved:** {datetime.datetime.now().strftime('%B %d, %Y at %I:%M %p')}
316
+ """
317
 
318
+ def _format_error_response(self, error_message: str) -> str:
319
+ """Format error response"""
320
+ return f"""
321
+ # 🕯️ Shabbat Candle Lighting Times
322
+
323
+ ## ⚠️ Error
324
+
325
+ {error_message}
326
+
327
+ ### 🔄 **Please try:**
328
+ 1. **Refresh** and try again
329
+ 2. **Check internet connection**
330
+ 3. **Use a different city name**
331
+ 4. **Visit Chabad.org directly:** [Candle Lighting Times](https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm)
332
+
333
+ ---
334
+
335
+ **Time:** {datetime.datetime.now().strftime('%B %d, %Y at %I:%M %p')}
336
+ """
337
+
338
+ def create_shabbat_interface():
339
+ """Create the Gradio interface for the Shabbat Candle Lighting Agent"""
340
 
341
+ agent = ShabbatCandleLightingAgent()
 
 
342
 
343
+ with gr.Blocks(title="🕯️ Shabbat Candle Lighting Times", theme=gr.themes.Soft()) as app:
 
 
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  gr.Markdown("""
346
+ # 🕯️ Shabbat Candle Lighting Times AI Agent
347
+
348
+ **Get accurate Shabbat candle lighting and Havdalah times for any city worldwide**
349
 
350
+ This AI agent searches Chabad.org and other reliable sources to find precise Jewish ritual times for your location.
 
 
351
  """)
352
 
353
+ with gr.Row():
354
+ with gr.Column():
355
+ gr.Markdown("### 📍 Location Input")
 
356
 
357
+ city_input = gr.Textbox(
358
+ label="🏙️ City Name",
359
+ placeholder="Enter city name (e.g., 'New York', 'London', 'Tel Aviv', 'Paris, France')",
360
+ info="Leave empty to auto-detect your location"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  )
 
 
 
 
362
 
363
+ auto_detect = gr.Checkbox(
364
+ label="🌐 Auto-detect my location",
365
+ value=True,
366
+ info="Automatically find your location if no city is entered"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
+ search_btn = gr.Button(
370
+ "🔍 Get Shabbat Times",
371
+ variant="primary",
372
+ size="lg"
373
  )
 
 
 
 
 
 
 
 
 
 
374
 
375
+ gr.Markdown("""
376
+ ### 💡 **Tips:**
377
+ - Use full city names for better results
378
+ - Include country for common city names (e.g., "Paris, France")
379
+ - Try nearest major city if your city isn't found
 
 
 
 
 
 
 
 
 
 
 
380
  """)
381
+
382
+ with gr.Column():
383
+ output_area = gr.Markdown(
384
+ label="📋 Shabbat Times Results",
385
+ value="👆 Enter a city name and click 'Get Shabbat Times' to begin"
386
+ )
387
+
388
+ # Example buttons for popular cities
389
+ gr.Markdown("### 🌍 Quick Search - Popular Cities:")
390
+
391
+ with gr.Row():
392
+ examples = [
393
+ ["New York", False],
394
+ ["London", False],
395
+ ["Jerusalem", False],
396
+ ["Los Angeles", False],
397
+ ["Tel Aviv", False],
398
+ ["Miami", False]
399
+ ]
400
+
401
+ example_btns = []
402
+ for city, auto in examples:
403
+ btn = gr.Button(f"📍 {city}", size="sm")
404
+ example_btns.append(btn)
405
+
406
+ # Button click handlers
407
+ search_btn.click(
408
+ fn=agent.get_shabbat_times,
409
+ inputs=[city_input, auto_detect],
410
+ outputs=output_area
411
+ )
412
+
413
+ # Example button handlers
414
+ for i, (city, auto) in enumerate(examples):
415
+ example_btns[i].click(
416
+ fn=lambda c=city: agent.get_shabbat_times(c, False),
417
+ outputs=output_area
418
+ )
419
+
420
+ # Resources and information
421
+ with gr.Accordion("📚 Resources & Information", open=False):
422
+ gr.Markdown("""
423
+ ### 🕯️ **About Shabbat Candle Lighting:**
424
+
425
+ **Candle Lighting Time:**
426
+ - Performed on Friday evening before sunset
427
+ - Typically 18 minutes before sunset (varies by community)
428
+ - Marks the beginning of Shabbat
429
+
430
+ **Havdalah Time:**
431
+ - Performed on Saturday evening after sunset
432
+ - When three medium-sized stars are visible in the sky
433
+ - Marks the end of Shabbat
434
+
435
+ ### 🌐 **Data Sources:**
436
+ - Primary: [Chabad.org](https://www.chabad.org/calendar/candlelighting_cdo/aid/6226/jewish/Accurate-Shabbat-Candle-Lighting-Times.htm)
437
+ - Alternative sources for backup
438
+ - Local astronomical calculations
439
+
440
+ ### ⚠️ **Important:**
441
+ - Always consult your local rabbi for specific customs
442
+ - Times may vary based on local Jewish community practices
443
+ - This tool is for reference only
444
+ """)
445
+
446
+ gr.Markdown("""
447
+ ---
448
+
449
+ **🙏 Shabbat Shalom!** | Built with ❤️ for the Jewish community worldwide
450
+
451
+ *For questions or issues, please consult your local Jewish community or rabbi.*
452
+ """)
453
 
454
  return app
455
 
456
  # Main execution
457
  if __name__ == "__main__":
458
+ app = create_shabbat_interface()
 
459
 
460
+ # Launch the application
461
  app.launch(
462
+ server_name="0.0.0.0",
463
+ server_port=7860,
464
+ share=False,
465
+ debug=False,
466
+ show_error=True
467
  )