SoufianeDahimi commited on
Commit
ed30e83
Β·
verified Β·
1 Parent(s): 99c7e64

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +28 -0
  2. README.md +5 -4
  3. app.py +1020 -0
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11
2
+
3
+ # Install system dependencies
4
+ RUN apt-get update && apt-get install -y \
5
+ curl \
6
+ && rm -rf /var/lib/apt/lists/*
7
+
8
+ # Install Ollama
9
+ RUN curl -fsSL https://ollama.com/install.sh | sh
10
+
11
+ # Set working directory
12
+ WORKDIR /code
13
+
14
+ # Copy requirements and install Python dependencies
15
+ COPY requirements.txt .
16
+ RUN pip install --no-cache-dir -r requirements.txt
17
+
18
+ # Copy application files
19
+ COPY . .
20
+
21
+ # Create directory for Ollama
22
+ RUN mkdir -p /root/.ollama
23
+
24
+ # Expose port
25
+ EXPOSE 7860
26
+
27
+ # Start Ollama service and then the app
28
+ CMD ollama serve & sleep 10 && ollama pull llm_hub/child_trauma_gemma && python app.py
README.md CHANGED
@@ -1,11 +1,12 @@
1
  ---
2
- title: Another Test
3
  emoji: πŸ“Š
4
- colorFrom: yellow
5
- colorTo: yellow
6
  sdk: docker
 
7
  pinned: false
8
- short_description: another test
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Testing Gradio Ollama
3
  emoji: πŸ“Š
4
+ colorFrom: gray
5
+ colorTo: gray
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
+ short_description: testing gradio with ollama using docker
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,1020 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import threading
3
+ import time
4
+ import subprocess
5
+ import gradio as gr
6
+ import json
7
+ import random
8
+ from datetime import datetime
9
+ import uuid
10
+ import requests
11
+ from requests.exceptions import ConnectionError, RequestException
12
+ from dotenv import load_dotenv
13
+ from supabase import create_client, Client
14
+ from ollama import chat
15
+ from pydantic import BaseModel
16
+
17
+ # Ollama setup for Docker spaces
18
+ print("Ollama should be running via Docker startup...")
19
+ time.sleep(5) # Give Ollama time to start
20
+
21
+ # Test Ollama connection
22
+ try:
23
+ # Simple test to see if Ollama is available
24
+ result = subprocess.run("ollama list", shell=True, capture_output=True, text=True)
25
+ print("Ollama status:", result.stdout)
26
+ print("Model should be available via Docker startup...")
27
+ except Exception as e:
28
+ print(f"Ollama check failed: {e}")
29
+
30
+ model_name = "llm_hub/child_trauma_gemma"
31
+
32
+ # Load environment variables
33
+ load_dotenv()
34
+
35
+ # Pydantic model for structured report generation
36
+ class RiskAssessment(BaseModel):
37
+ parent_observations: str
38
+ ai_analysis: str
39
+ severity_score: int
40
+ risk_indicators: list[str]
41
+ cultural_context: str
42
+
43
+ class EnhancedTraumaAssessmentApp:
44
+ def __init__(self):
45
+ self.report_data = {
46
+ "child_info": {
47
+ "name": "",
48
+ "age": 0,
49
+ "gender": "",
50
+ "location": ""
51
+ },
52
+ "assessment_data": {
53
+ "parent_observations": "",
54
+ "ai_analysis": "",
55
+ "severity_score": 0,
56
+ "risk_indicators": [],
57
+ "cultural_context": ""
58
+ },
59
+ "media_attachments": {
60
+ "drawings": [],
61
+ "audio_recordings": [],
62
+ "photos": []
63
+ },
64
+ "mobile_app_id": str(uuid.uuid4()),
65
+ "session_start": datetime.now().isoformat(),
66
+ "conversation_history": []
67
+ }
68
+ self.is_onboarded = False
69
+ self.submitted_report_id = None
70
+ self.polling_active = False
71
+ self.ollama_conversation = [] # Track conversation for the model
72
+
73
+ # Initialize Supabase client
74
+ self.supabase_url = os.getenv("NEXT_PUBLIC_SUPABASE_URL")
75
+ self.supabase_key = os.getenv("NEXT_PUBLIC_SUPABASE_ANON_KEY")
76
+
77
+ if self.supabase_url and self.supabase_key:
78
+ self.supabase: Client = create_client(self.supabase_url, self.supabase_key)
79
+ else:
80
+ self.supabase = None
81
+ print("⚠️ Supabase credentials not found in .env file")
82
+
83
+ def complete_onboarding(self, child_name, child_age, child_gender, child_location):
84
+ """Complete the onboarding process and store child info"""
85
+ if not all([child_name, child_age, child_gender, child_location]):
86
+ return False, "Please fill in all required information about your child."
87
+
88
+ self.report_data["child_info"] = {
89
+ "name": child_name,
90
+ "age": int(child_age),
91
+ "gender": child_gender,
92
+ "location": child_location
93
+ }
94
+ self.is_onboarded = True
95
+
96
+ # Generate cultural context based on location
97
+ self.report_data["assessment_data"]["cultural_context"] = self.generate_cultural_context(child_location)
98
+
99
+ return True, f"Welcome! I'm ready to help you with {child_name}'s assessment."
100
+
101
+ def generate_cultural_context(self, location):
102
+ """Generate appropriate cultural context based on location"""
103
+ location_lower = location.lower()
104
+ if any(keyword in location_lower for keyword in ['gaza', 'palestine', 'west bank']):
105
+ return "Assessment conducted considering ongoing conflict exposure and displacement trauma"
106
+ elif any(keyword in location_lower for keyword in ['ukraine', 'kyiv', 'kharkiv', 'mariupol']):
107
+ return "Assessment considering war-related trauma and displacement from conflict zones"
108
+ elif any(keyword in location_lower for keyword in ['syria', 'lebanon', 'jordan']):
109
+ return "Assessment considering refugee experience and cultural adaptation challenges"
110
+ else:
111
+ return f"Assessment conducted with consideration for local cultural context in {location}"
112
+
113
+ def add_message(self, history, message):
114
+ """Add user message with multimodal support"""
115
+ if not self.is_onboarded:
116
+ return history, gr.MultimodalTextbox(value=None, interactive=False)
117
+
118
+ # Handle file uploads
119
+ if message.get("files"):
120
+ for file in message["files"]:
121
+ file_type = self.classify_file_type(file)
122
+ history.append({
123
+ "role": "user",
124
+ "content": {"path": file}
125
+ })
126
+
127
+ # Store in report data
128
+ if file_type == "image":
129
+ # Determine if it's a drawing or photo based on content analysis
130
+ attachment_type = "drawings" if "draw" in file.lower() else "photos"
131
+ self.report_data["media_attachments"][attachment_type].append({
132
+ "path": file,
133
+ "timestamp": datetime.now().isoformat()
134
+ })
135
+ print(f"Image file detected: {file}")
136
+
137
+ # Handle text message
138
+ if message.get("text"):
139
+ history.append({
140
+ "role": "user",
141
+ "content": message["text"]
142
+ })
143
+ # Add to conversation history for model
144
+ self.ollama_conversation.append({
145
+ "role": "user",
146
+ "content": message["text"]
147
+ })
148
+ # Add to parent observations
149
+ current_obs = self.report_data["assessment_data"]["parent_observations"]
150
+ self.report_data["assessment_data"]["parent_observations"] = (
151
+ current_obs + " " + message["text"] if current_obs else message["text"]
152
+ )
153
+
154
+ # Store conversation history
155
+ self.report_data["conversation_history"] = history
156
+ return history, gr.MultimodalTextbox(value=None, interactive=False)
157
+
158
+ def classify_file_type(self, file_path):
159
+ """Classify uploaded file type"""
160
+ if file_path.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp')):
161
+ return "image"
162
+ else:
163
+ return "other"
164
+
165
+ def bot_response(self, history):
166
+ """Generate bot response using Ollama model"""
167
+ if not history or not self.is_onboarded:
168
+ return
169
+
170
+ # Get the last user message
171
+ last_message = ""
172
+ has_image = False
173
+ image_path = None
174
+
175
+ for msg in reversed(history):
176
+ if msg["role"] == "user":
177
+ if isinstance(msg["content"], str):
178
+ last_message = msg["content"]
179
+ break
180
+ elif isinstance(msg["content"], dict) and "path" in msg["content"]:
181
+ has_image = True
182
+ image_path = msg["content"]["path"]
183
+ break
184
+
185
+ # Prepare message for Ollama
186
+ if has_image and image_path:
187
+ # Handle image input
188
+ try:
189
+ response = chat(
190
+ model=model_name,
191
+ messages=[{
192
+ 'role': 'user',
193
+ 'content': f'I am sharing an image related to my child {self.report_data["child_info"]["name"]}\'s situation. Please analyze this image in the context of trauma assessment and respond empathetically.',
194
+ 'images': [image_path],
195
+ }]
196
+ )
197
+ response_text = response.message.content
198
+ except Exception as e:
199
+ response_text = f"I can see you've shared an image. Thank you for providing this visual information about {self.report_data['child_info']['name']}. Visual expressions can tell us a lot about how children process their experiences. Could you tell me more about when this was created or what you'd like me to know about it?"
200
+ print(f"Ollama image error: {e}")
201
+ else:
202
+ # Handle text conversation
203
+ try:
204
+ response = chat(
205
+ model=model_name,
206
+ messages=self.ollama_conversation
207
+ )
208
+ response_text = response.message.content
209
+ except Exception as e:
210
+ response_text = f"Thank you for sharing that with me. I understand this is a difficult time for you and {self.report_data['child_info']['name']}. Could you tell me more about what you're observing?"
211
+ print(f"Ollama text error: {e}")
212
+
213
+ # Add assistant response to conversation history
214
+ self.ollama_conversation.append({
215
+ "role": "assistant",
216
+ "content": response_text
217
+ })
218
+
219
+ # Start bot response
220
+ history.append({"role": "assistant", "content": ""})
221
+
222
+ # Stream the response
223
+ for character in response_text:
224
+ history[-1]["content"] += character
225
+ time.sleep(0.02)
226
+ yield history
227
+
228
+ def generate_comprehensive_report(self, progress_callback=None):
229
+ """Generate comprehensive assessment report using Ollama structured output"""
230
+ if not self.is_onboarded:
231
+ return "Please complete the initial assessment form first."
232
+
233
+ if not self.ollama_conversation:
234
+ return "Please have a conversation first before generating a report."
235
+
236
+ if progress_callback:
237
+ progress_callback("πŸ€– Analyzing conversation with AI...")
238
+
239
+ try:
240
+ # Generate structured assessment using Ollama
241
+ assessment_prompt = f"""Based on our conversation about {self.report_data['child_info']['name']}, a {self.report_data['child_info']['age']}-year-old {self.report_data['child_info']['gender']} from {self.report_data['child_info']['location']}, generate a comprehensive trauma risk assessment report.
242
+
243
+ Include:
244
+ - Parent observations summary from our conversation
245
+ - AI analysis of trauma indicators
246
+ - Severity score (1-10 scale)
247
+ - List of risk indicators identified
248
+ - Cultural context considering the child's location and circumstances
249
+
250
+ Consider the conversation history and any cultural factors relevant to {self.report_data['child_info']['location']}."""
251
+
252
+ if progress_callback:
253
+ progress_callback("🧠 AI is generating structured assessment...")
254
+
255
+ response = chat(
256
+ model=model_name,
257
+ messages=[{'role': 'user', 'content': assessment_prompt}],
258
+ format=RiskAssessment.model_json_schema(),
259
+ options={'temperature': 0}
260
+ )
261
+
262
+ if progress_callback:
263
+ progress_callback("πŸ“Š Processing assessment data...")
264
+
265
+ # Parse structured response
266
+ assessment = RiskAssessment.model_validate_json(response.message.content)
267
+
268
+ # Update report data with AI-generated assessment
269
+ self.report_data["assessment_data"]["parent_observations"] = assessment.parent_observations
270
+ self.report_data["assessment_data"]["ai_analysis"] = assessment.ai_analysis
271
+ self.report_data["assessment_data"]["severity_score"] = assessment.severity_score
272
+ self.report_data["assessment_data"]["risk_indicators"] = assessment.risk_indicators
273
+ self.report_data["assessment_data"]["cultural_context"] = assessment.cultural_context
274
+
275
+ if progress_callback:
276
+ progress_callback("πŸ“‹ Formatting final report...")
277
+
278
+ except Exception as e:
279
+ print(f"Ollama structured output error: {e}")
280
+ if progress_callback:
281
+ progress_callback("⚠️ Using fallback assessment...")
282
+ # Fallback to basic assessment
283
+ self.report_data["assessment_data"]["severity_score"] = 6
284
+ self.report_data["assessment_data"]["risk_indicators"] = ["sleep disturbances", "behavioral changes", "anxiety"]
285
+
286
+ # Generate formatted report
287
+ child_info = self.report_data["child_info"]
288
+ assessment_data = self.report_data["assessment_data"]
289
+ media_attachments = self.report_data["media_attachments"]
290
+ severity = assessment_data["severity_score"]
291
+ risk_indicators = assessment_data["risk_indicators"]
292
+
293
+ return f"""# πŸ” COMPREHENSIVE TRAUMA ASSESSMENT REPORT
294
+
295
+ **Generated:** {datetime.now().strftime("%B %d, %Y at %H:%M")}
296
+ **Assessment ID:** {self.report_data["mobile_app_id"][:8]}
297
+ **Confidentiality Level:** Protected Health Information
298
+ **Platform:** Child Trauma Assessment AI
299
+
300
+ ---
301
+
302
+ ## πŸ‘€ CHILD INFORMATION
303
+
304
+ **Name:** {child_info["name"]}
305
+ **Age:** {child_info["age"]} years old
306
+ **Gender:** {child_info["gender"].title()}
307
+ **Location:** {child_info["location"]}
308
+ **Assessment Date:** {datetime.now().strftime("%B %d, %Y")}
309
+
310
+ ---
311
+
312
+ ## πŸ‘₯ PARENT OBSERVATIONS
313
+
314
+ {assessment_data["parent_observations"]}
315
+
316
+ **Session Details:**
317
+ - **Duration:** {len(self.report_data["conversation_history"])} message exchanges
318
+ - **Media Provided:** {len(media_attachments["drawings"])} drawings, {len(media_attachments["photos"])} photographs
319
+
320
+ ---
321
+
322
+ ## 🧠 AI ANALYSIS
323
+
324
+ {assessment_data["ai_analysis"]}
325
+
326
+ **Behavioral Patterns Identified:**
327
+ {chr(10).join([f"β€’ {indicator}" for indicator in risk_indicators])}
328
+
329
+ ---
330
+
331
+ ## ⚠️ SEVERITY ASSESSMENT
332
+
333
+ **Severity Score:** {severity}/10
334
+ **Risk Level:** {"🟑 Moderate Risk" if severity < 7 else "πŸ”΄ High Risk - Urgent Intervention Recommended"}
335
+ **Clinical Priority:** {"Standard referral appropriate" if severity < 7 else "Expedited professional evaluation needed"}
336
+
337
+ ---
338
+
339
+ ## 🌍 CULTURAL CONTEXT
340
+
341
+ {assessment_data["cultural_context"]}
342
+
343
+ This assessment considers the cultural and environmental factors specific to {child_info["location"]}, including region-specific trauma expressions, family dynamics, and community support systems.
344
+
345
+ ---
346
+
347
+ ## πŸ“‹ CLINICAL RECOMMENDATIONS
348
+
349
+ **Immediate Actions:**
350
+ 1. Schedule comprehensive evaluation with licensed child trauma specialist
351
+ 2. Ensure stable, predictable environment for {child_info["name"]}
352
+ 3. Implement safety planning and crisis contact protocols
353
+
354
+ **Therapeutic Interventions:**
355
+ 1. Begin trauma-focused cognitive behavioral therapy (TF-CBT)
356
+ 2. Consider family therapy to strengthen support systems
357
+ 3. Monitor sleep, appetite, and behavioral patterns daily
358
+
359
+ **Cultural Considerations:**
360
+ 1. Engage culturally competent mental health services
361
+ 2. Incorporate traditional coping mechanisms where appropriate
362
+ 3. Consider community-based support resources
363
+
364
+ **Follow-up:**
365
+ - Initial professional evaluation within 1-2 weeks
366
+ - Regular monitoring and assessment as recommended by treating clinician
367
+
368
+ ---
369
+
370
+ ## βš–οΈ IMPORTANT DISCLAIMERS
371
+
372
+ - **Preliminary Screening Tool:** This AI-generated assessment is for screening purposes only and does NOT constitute a clinical diagnosis
373
+ - **Professional Validation Required:** All findings must be validated by licensed mental health professionals
374
+ - **Emergency Protocol:** For immediate safety concerns, contact emergency services immediately
375
+ - **Clinical Judgment:** AI analysis should supplement, not replace, professional clinical assessment
376
+
377
+ **Report Generated:** {datetime.now().isoformat()}
378
+ **Next Review Recommended:** {(datetime.now()).strftime("%B %d, %Y")} (2 weeks)
379
+ """
380
+
381
+ def push_report_to_care_bridge(self, base_url="https://care-bridge-platform-7vs1.vercel.app"):
382
+ """Push the generated report to the Care Bridge platform."""
383
+ if not self.is_onboarded:
384
+ return False, "Please complete the initial assessment form first."
385
+
386
+ if not self.report_data["conversation_history"]:
387
+ return False, "Please have a conversation first before pushing a report."
388
+
389
+ # Prepare data in the format expected by Care Bridge API
390
+ api_data = {
391
+ "child_info": {
392
+ "age": self.report_data["child_info"]["age"],
393
+ "gender": self.report_data["child_info"]["gender"].lower(),
394
+ "location": self.report_data["child_info"]["location"]
395
+ },
396
+ "assessment_data": {
397
+ "parent_observations": self.report_data["assessment_data"]["parent_observations"],
398
+ "ai_analysis": self.report_data["assessment_data"]["ai_analysis"],
399
+ "severity_score": self.report_data["assessment_data"]["severity_score"],
400
+ "risk_indicators": self.report_data["assessment_data"]["risk_indicators"],
401
+ "cultural_context": self.report_data["assessment_data"]["cultural_context"]
402
+ },
403
+ "media_attachments": self.report_data["media_attachments"],
404
+ "mobile_app_id": self.report_data["mobile_app_id"]
405
+ }
406
+
407
+ try:
408
+ url = f"{base_url}/api/reports"
409
+ headers = {"Content-Type": "application/json"}
410
+
411
+ response = requests.post(url, json=api_data, headers=headers, timeout=10)
412
+
413
+ if response.status_code == 201:
414
+ result = response.json()
415
+ report_id = result.get('id', 'Unknown')
416
+ # Store the report ID for polling
417
+ self.submitted_report_id = report_id
418
+ # Start polling for responses
419
+ self.start_response_polling()
420
+ return True, f"βœ… Report successfully pushed to Care Bridge Platform!\nπŸ“‹ Report ID: {report_id}\nπŸ”„ Now monitoring for specialist response..."
421
+ else:
422
+ return False, f"❌ API Error: {response.status_code} - {response.text}"
423
+
424
+ except ConnectionError:
425
+ return False, "❌ Could not connect to Care Bridge Platform. Please check if the platform is running."
426
+ except requests.exceptions.Timeout:
427
+ return False, "❌ Request timed out. Please try again."
428
+ except RequestException as e:
429
+ return False, f"❌ Network error: {str(e)}"
430
+ except Exception as e:
431
+ return False, f"❌ Unexpected error: {str(e)}"
432
+
433
+ def start_response_polling(self):
434
+ """Start polling for specialist responses in a background thread."""
435
+ if not self.supabase or not self.submitted_report_id:
436
+ print("⚠️ Cannot start polling: Missing Supabase connection or report ID")
437
+ return
438
+
439
+ if self.polling_active:
440
+ print("ℹ️ Polling already active")
441
+ return # Already polling
442
+
443
+ self.polling_active = True
444
+ print(f"πŸ”„ Starting background polling for report ID: {self.submitted_report_id}")
445
+ polling_thread = threading.Thread(target=self._poll_for_response, daemon=True)
446
+ polling_thread.start()
447
+
448
+ def _poll_for_response(self):
449
+ """Poll Supabase for specialist responses."""
450
+ max_polls = 120 # Poll for 10 minutes (120 * 5 seconds)
451
+ poll_count = 0
452
+ print("Starting polling for response...")
453
+ while self.polling_active and poll_count < max_polls:
454
+ try:
455
+ # Check for response in Supabase
456
+ print("Polling for response...")
457
+ response = self.supabase.table("responses").select("*").eq("report_id", self.submitted_report_id).execute()
458
+
459
+ if response.data and len(response.data) > 0:
460
+ # Response found!
461
+ specialist_response = response.data[0]
462
+ self.specialist_response = specialist_response
463
+ self.get_specialist_response()
464
+ self.polling_active = False
465
+ break
466
+
467
+ # Wait 5 seconds before next poll
468
+ time.sleep(5)
469
+ poll_count += 1
470
+
471
+ except Exception as e:
472
+ print(f"Error polling for response: {e}")
473
+ time.sleep(5)
474
+ poll_count += 1
475
+
476
+ # Stop polling after max attempts
477
+ if poll_count >= max_polls:
478
+ self.polling_active = False
479
+
480
+ def get_specialist_response(self):
481
+ """Get the specialist response if available."""
482
+ if hasattr(self, 'specialist_response'):
483
+ response = self.specialist_response
484
+
485
+ urgency_color = {
486
+ 'low': '🟒',
487
+ 'medium': '🟑',
488
+ 'high': '🟠',
489
+ 'critical': 'πŸ”΄'
490
+ }
491
+
492
+ urgency_emoji = urgency_color.get(response['urgency_level'], 'βšͺ')
493
+
494
+ formatted_response = f"""
495
+ # πŸ‘¨β€βš•οΈ SPECIALIST RESPONSE RECEIVED
496
+
497
+ **Response Date:** {response['response_date'][:19].replace('T', ' ')}
498
+ **Specialist ID:** {response['psychologist_id']}
499
+ **Urgency Level:** {urgency_emoji} {response['urgency_level'].upper()}
500
+
501
+ ---
502
+
503
+ ## πŸ“ PSYCHOLOGIST NOTES
504
+
505
+ {response['psychologist_notes']}
506
+
507
+ ---
508
+
509
+ ## πŸ’‘ RECOMMENDATIONS
510
+
511
+ """
512
+
513
+ if isinstance(response['recommendations'], dict):
514
+ for key, value in response['recommendations'].items():
515
+ formatted_response += f"**{key.replace('_', ' ').title()}:** {value}\n\n"
516
+ else:
517
+ formatted_response += str(response['recommendations'])
518
+
519
+ return True, formatted_response
520
+
521
+ return False, "No specialist response available yet. Still monitoring..."
522
+
523
+ # Initialize enhanced app
524
+ app = EnhancedTraumaAssessmentApp()
525
+
526
+ # Enhanced CSS with onboarding styles
527
+ css = """
528
+ /* Main container styling */
529
+ .gradio-container {
530
+ max-width: 900px !important;
531
+ margin: 0 auto !important;
532
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
533
+ }
534
+
535
+ /* Onboarding specific styles */
536
+ .onboarding-container {
537
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
538
+ color: white;
539
+ padding: 40px 30px;
540
+ border-radius: 20px;
541
+ margin: 20px 0;
542
+ text-align: center;
543
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
544
+ }
545
+
546
+ .welcome-form {
547
+ background: white;
548
+ color: #333;
549
+ padding: 30px;
550
+ border-radius: 15px;
551
+ margin: 20px 0;
552
+ box-shadow: 0 5px 20px rgba(0,0,0,0.1);
553
+ }
554
+
555
+ .form-section {
556
+ margin: 20px 0;
557
+ text-align: left;
558
+ }
559
+
560
+ .form-section label {
561
+ font-weight: 600;
562
+ color: #2d3436;
563
+ margin-bottom: 8px;
564
+ display: block;
565
+ }
566
+
567
+ /* Header styling */
568
+ .header-container {
569
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
570
+ color: white;
571
+ padding: 30px 20px;
572
+ border-radius: 15px;
573
+ margin-bottom: 25px;
574
+ text-align: center;
575
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
576
+ }
577
+
578
+ /* Status indicators */
579
+ .status-success {
580
+ background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
581
+ border-left: 4px solid #00b894;
582
+ padding: 15px 20px;
583
+ border-radius: 8px;
584
+ margin: 15px 0;
585
+ color: #00b894;
586
+ font-weight: 500;
587
+ }
588
+
589
+ .status-warning {
590
+ background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
591
+ border-left: 4px solid #f39c12;
592
+ padding: 15px 20px;
593
+ border-radius: 8px;
594
+ margin: 15px 0;
595
+ color: #e67e22;
596
+ }
597
+
598
+ .status-info {
599
+ background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
600
+ border-left: 4px solid #74b9ff;
601
+ padding: 15px 20px;
602
+ border-radius: 8px;
603
+ margin: 15px 0;
604
+ color: #0984e3;
605
+ }
606
+
607
+ /* Button styling */
608
+ .primary-button {
609
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
610
+ border: none !important;
611
+ color: white !important;
612
+ padding: 15px 30px !important;
613
+ border-radius: 25px !important;
614
+ font-weight: 600 !important;
615
+ font-size: 16px !important;
616
+ transition: all 0.3s ease !important;
617
+ width: 100% !important;
618
+ }
619
+
620
+ .primary-button:hover {
621
+ transform: translateY(-2px) !important;
622
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4) !important;
623
+ }
624
+
625
+ /* Chat interface styling */
626
+ .chat-container {
627
+ background: white;
628
+ border-radius: 15px;
629
+ padding: 20px;
630
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
631
+ margin-bottom: 20px;
632
+ }
633
+
634
+ .child-info-display {
635
+ background: linear-gradient(135deg, #ddd6fe 0%, #e0e7ff 100%);
636
+ border: 1px solid #c4b5fd;
637
+ padding: 15px 20px;
638
+ border-radius: 10px;
639
+ margin: 15px 0;
640
+ color: #5b21b6;
641
+ }
642
+
643
+ /* Mobile responsiveness */
644
+ @media (max-width: 768px) {
645
+ .gradio-container {
646
+ max-width: 100% !important;
647
+ margin: 0 10px !important;
648
+ }
649
+
650
+ .onboarding-container {
651
+ padding: 25px 20px;
652
+ margin: 10px 0;
653
+ }
654
+
655
+ .welcome-form {
656
+ padding: 20px;
657
+ margin: 15px 0;
658
+ }
659
+ }
660
+ """
661
+
662
+ # Build enhanced Gradio interface with onboarding
663
+ with gr.Blocks(css=css, title="Child Trauma Assessment - Professional Support", theme=gr.themes.Soft()) as demo:
664
+
665
+ # Session state for controlling interface
666
+ onboarding_complete = gr.State(False)
667
+
668
+ # Welcome/Onboarding Interface
669
+ with gr.Column(visible=True) as onboarding_section:
670
+ gr.HTML("""
671
+ <div class="onboarding-container">
672
+ <h1>πŸ€— Welcome to Child Trauma Assessment AI</h1>
673
+ <p>Professional-grade support for families and children in crisis</p>
674
+ <br>
675
+ <h3>Let's start by learning about your child</h3>
676
+ </div>
677
+ """)
678
+
679
+ with gr.Column(elem_classes=["welcome-form"]):
680
+ gr.HTML("<h2 style='text-align: center; color: #667eea; margin-bottom: 25px;'>πŸ“ Child Information Form</h2>")
681
+
682
+ with gr.Row():
683
+ child_name = gr.Textbox(
684
+ label="Child's Name (First name only for privacy)",
685
+ placeholder="e.g., Sarah, Ahmed, Oleksandr",
686
+ elem_classes=["form-section"]
687
+ )
688
+ child_age = gr.Number(
689
+ label="Child's Age",
690
+ minimum=2,
691
+ maximum=18,
692
+ value=8,
693
+ elem_classes=["form-section"]
694
+ )
695
+
696
+ with gr.Row():
697
+ child_gender = gr.Dropdown(
698
+ label="Gender",
699
+ choices=["Female", "Male", "Prefer not to say"],
700
+ value="Female",
701
+ elem_classes=["form-section"]
702
+ )
703
+ child_location = gr.Textbox(
704
+ label="Current Location (City/Region)",
705
+ placeholder="e.g., Gaza, Kyiv, Aleppo, London",
706
+ elem_classes=["form-section"]
707
+ )
708
+
709
+ gr.HTML("""
710
+ <div class="status-info" style="margin: 20px 0;">
711
+ <strong>πŸ”’ Privacy Notice:</strong> This information is used only to personalize the assessment
712
+ and provide culturally appropriate support. No personal data is stored permanently.
713
+ </div>
714
+ """)
715
+
716
+ start_assessment_btn = gr.Button(
717
+ "πŸš€ Begin Assessment",
718
+ elem_classes=["primary-button"],
719
+ variant="primary",
720
+ size="lg"
721
+ )
722
+
723
+ onboarding_status = gr.HTML()
724
+
725
+ # Main Assessment Interface (hidden initially)
726
+ with gr.Column(visible=False) as main_interface:
727
+ # Child info display
728
+ child_info_display = gr.HTML()
729
+
730
+ with gr.Tab("πŸ’¬ Confidential Consultation"):
731
+ gr.HTML("""
732
+ <div class="status-info">
733
+ <strong>πŸ€– REAL AI MODEL:</strong> This platform uses our fine-tuned Gemma 3N model for authentic trauma assessment conversations.
734
+ <br><br>
735
+ <strong>πŸ’‘ Try These Features:</strong>
736
+ <br>
737
+ β€’ Start a conversation: "Hello, I'm worried about my child's recent behavior changes"
738
+ <br>
739
+ β€’ Upload images (child photos, drawings) for AI visual analysis
740
+ <br>
741
+ β€’ Use different languages - the model supports Arabic, Ukrainian, and English
742
+ <br>
743
+ β€’ Generate structured reports with AI-powered assessment insights
744
+ <br><br>
745
+ <strong>πŸ”’ Privacy:</strong> All conversations are processed securely. Audio support coming soon.
746
+ </div>
747
+ """)
748
+
749
+ chatbot = gr.Chatbot(
750
+ label="AI Trauma Assessment Specialist",
751
+ height=500,
752
+ bubble_full_width=False,
753
+ type="messages",
754
+ show_label=False,
755
+ elem_classes=["chat-container"]
756
+ )
757
+
758
+ chat_input = gr.MultimodalTextbox(
759
+ interactive=True,
760
+ file_count="multiple",
761
+ placeholder="Share your concerns here... ΩŠΩ…ΩƒΩ†Ωƒ Ψ§Ω„ΩƒΨͺΨ§Ψ¨Ψ© Ψ¨Ψ§Ω„ΨΉΨ±Ψ¨ΩŠΨ© β€’ ΠœΠΎΠΆΠ΅Ρ‚Π΅ писати ΡƒΠΊΡ€Π°Ρ—Π½ΡΡŒΠΊΠΎΡŽ",
762
+ show_label=False,
763
+ sources=["upload"] # Removed microphone - audio not yet supported
764
+ )
765
+
766
+ with gr.Row():
767
+ clear_btn = gr.Button("πŸ—‘οΈ New Conversation", variant="secondary", size="sm")
768
+ gr.HTML('<div style="flex-grow: 1;"></div>')
769
+
770
+ with gr.Tab("πŸ“‹ Professional Assessment Report"):
771
+ gr.HTML("""
772
+ <div class="status-warning">
773
+ <strong>⚠️ Professional Use Only:</strong> This AI-generated report is a preliminary screening tool.
774
+ It must be reviewed by licensed mental health professionals.
775
+ </div>
776
+ """)
777
+
778
+ generate_report_btn = gr.Button(
779
+ "πŸ“Š Generate Comprehensive Assessment",
780
+ variant="primary",
781
+ size="lg",
782
+ elem_classes=["primary-button"]
783
+ )
784
+
785
+ # Add progress indicator
786
+ progress_status = gr.HTML()
787
+
788
+ report_output = gr.Markdown()
789
+
790
+ with gr.Row():
791
+ save_report_btn = gr.Button("πŸ’Ύ Save Report", variant="secondary")
792
+ push_care_bridge_btn = gr.Button("πŸŒ‰ Push to Care Bridge", variant="primary")
793
+ gr.Button("πŸ“§ Email to Professional", variant="secondary", interactive=False)
794
+
795
+ save_status = gr.HTML()
796
+ care_bridge_status = gr.HTML()
797
+
798
+ with gr.Tab("πŸ‘¨β€βš•οΈ Specialist Response"):
799
+ gr.HTML("""
800
+ <div class="status-info">
801
+ <strong>πŸ”„ Background Monitoring:</strong> Once you submit a report, we automatically monitor for specialist responses in the background.
802
+ Click the button below to check for new responses.
803
+ </div>
804
+ """)
805
+
806
+ check_response_btn = gr.Button(
807
+ "πŸ” Check for Specialist Response",
808
+ variant="secondary",
809
+ size="lg"
810
+ )
811
+
812
+ specialist_response_output = gr.Markdown()
813
+ response_status = gr.HTML()
814
+
815
+ with gr.Tab("πŸ“– Resources & Information"):
816
+ gr.Markdown("""
817
+ ## 🎯 How This Assessment Works
818
+
819
+ Our AI specialist uses evidence-based approaches tailored to your child's specific situation:
820
+
821
+ ### πŸ“ **Personalized Assessment**
822
+ - Responses are customized based on your child's age, gender, and location
823
+ - Cultural context is considered throughout the evaluation
824
+ - All interactions are stored securely for comprehensive reporting
825
+
826
+ ### πŸ” **What We Analyze**
827
+ - Behavioral pattern changes specific to your child's developmental stage
828
+ - Cultural expressions of trauma and stress
829
+ - Family dynamics and support systems
830
+ - Environmental factors affecting recovery
831
+
832
+ ### πŸ“Š **Structured Data Collection**
833
+ All information is organized into a comprehensive clinical format:
834
+ - Child demographics and context
835
+ - Detailed parent observations
836
+ - AI analysis and risk assessment
837
+ - Multimedia evidence (drawings, voice recordings, photos)
838
+ - Cultural considerations and recommendations
839
+
840
+ ## πŸŒ‰ **Care Bridge Platform Integration**
841
+
842
+ This assessment tool integrates with the Care Bridge Platform to:
843
+ - **Share Reports**: Securely transmit assessment data to professional networks
844
+ - **Track Progress**: Maintain longitudinal care records
845
+ - **Coordinate Care**: Enable multi-disciplinary team collaboration
846
+ - **Emergency Response**: Alert crisis intervention teams when needed
847
+ """)
848
+
849
+ # Event handlers
850
+ def handle_onboarding(name, age, gender, location):
851
+ success, message = app.complete_onboarding(name, age, gender, location)
852
+
853
+ if success:
854
+ child_display = f"""
855
+ <div class="child-info-display">
856
+ <strong>πŸ‘€ Assessment for:</strong> {name}, {int(age)} years old ({gender}) β€’ πŸ“ {location}
857
+ </div>
858
+ """
859
+ return (
860
+ gr.Column(visible=False), # Hide onboarding
861
+ gr.Column(visible=True), # Show main interface
862
+ child_display,
863
+ f'<div class="status-success">{message}</div>'
864
+ )
865
+ else:
866
+ return (
867
+ gr.Column(visible=True), # Keep onboarding visible
868
+ gr.Column(visible=False), # Keep main interface hidden
869
+ "",
870
+ f'<div class="status-warning">❌ {message}</div>'
871
+ )
872
+
873
+ # Onboarding completion
874
+ start_assessment_btn.click(
875
+ handle_onboarding,
876
+ inputs=[child_name, child_age, child_gender, child_location],
877
+ outputs=[onboarding_section, main_interface, child_info_display, onboarding_status]
878
+ )
879
+
880
+ # Conversation handling
881
+ def handle_conversation():
882
+ chat_msg = chat_input.submit(
883
+ app.add_message,
884
+ [chatbot, chat_input],
885
+ [chatbot, chat_input]
886
+ )
887
+ bot_msg = chat_msg.then(
888
+ app.bot_response,
889
+ chatbot,
890
+ chatbot
891
+ )
892
+ bot_msg.then(
893
+ lambda: gr.MultimodalTextbox(interactive=True),
894
+ None,
895
+ [chat_input]
896
+ )
897
+
898
+ handle_conversation()
899
+
900
+ # Clear conversation
901
+ def clear_conversation():
902
+ app.report_data["conversation_history"] = []
903
+ app.report_data["assessment_data"]["parent_observations"] = ""
904
+ app.report_data["assessment_data"]["ai_analysis"] = ""
905
+ app.report_data["media_attachments"] = {"drawings": [], "audio_recordings": [], "photos": []}
906
+ return [], gr.MultimodalTextbox(value=None, interactive=True)
907
+
908
+ clear_btn.click(
909
+ clear_conversation,
910
+ outputs=[chatbot, chat_input]
911
+ )
912
+
913
+ # Generate report with progress updates
914
+ def generate_report_with_progress():
915
+ # Show initial progress
916
+ progress_updates = []
917
+
918
+ def update_progress(message):
919
+ progress_updates.append(f'<div class="status-info">{message}</div>')
920
+ return progress_updates[-1]
921
+
922
+ # Generate report with progress callback
923
+ try:
924
+ progress = update_progress("πŸš€ Starting assessment generation...")
925
+ yield "", progress # Empty report, show progress
926
+
927
+ report = app.generate_comprehensive_report(progress_callback=update_progress)
928
+
929
+ final_progress = update_progress("βœ… Assessment completed!")
930
+ yield report, final_progress
931
+
932
+ # Clear progress after 3 seconds
933
+ time.sleep(3)
934
+ yield report, ""
935
+
936
+ except Exception as e:
937
+ error_progress = f'<div class="status-warning">❌ Error: {str(e)}</div>'
938
+ yield "", error_progress
939
+
940
+ generate_report_btn.click(
941
+ generate_report_with_progress,
942
+ outputs=[report_output, progress_status]
943
+ )
944
+
945
+ # Save report
946
+ def save_report_with_data(report_content):
947
+ if not report_content or "Please complete" in report_content:
948
+ return "❌ No report available to save."
949
+
950
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
951
+
952
+ # Save markdown report
953
+ report_filename = f"trauma_report_{app.report_data['child_info']['name']}_{timestamp}.md"
954
+
955
+ # Save structured data
956
+ data_filename = f"assessment_data_{app.report_data['child_info']['name']}_{timestamp}.json"
957
+
958
+ try:
959
+ with open(report_filename, 'w', encoding='utf-8') as f:
960
+ f.write(report_content)
961
+
962
+ with open(data_filename, 'w', encoding='utf-8') as f:
963
+ json.dump(app.report_data, f, indent=2, ensure_ascii=False, default=str)
964
+
965
+ return f"βœ… Report saved as: **{report_filename}**<br>πŸ“Š Data saved as: **{data_filename}**"
966
+ except Exception as e:
967
+ return f"❌ Error saving files: {str(e)}"
968
+
969
+ save_report_btn.click(
970
+ save_report_with_data,
971
+ inputs=[report_output],
972
+ outputs=[save_status]
973
+ )
974
+
975
+ # Push report to Care Bridge
976
+ def push_to_care_bridge():
977
+ success, message = app.push_report_to_care_bridge()
978
+ status_class = "status-success" if success else "status-warning"
979
+ return f'<div class="{status_class}">{message}</div>'
980
+
981
+ push_care_bridge_btn.click(
982
+ push_to_care_bridge,
983
+ outputs=[care_bridge_status]
984
+ )
985
+
986
+ # Check for specialist response
987
+ def check_for_response():
988
+ has_response, response_content = app.get_specialist_response()
989
+ if has_response:
990
+ return response_content, '<div class="status-success">βœ… Specialist response received!</div>'
991
+ elif app.polling_active:
992
+ return "", '<div class="status-info">πŸ”„ Still monitoring for specialist response...</div>'
993
+ elif app.submitted_report_id:
994
+ return "", '<div class="status-warning">⏸️ Monitoring stopped. No response received within time limit.</div>'
995
+ else:
996
+ return "", '<div class="status-warning">ℹ️ Submit a report first to check for responses.</div>'
997
+
998
+ check_response_btn.click(
999
+ check_for_response,
1000
+ outputs=[specialist_response_output, response_status]
1001
+ )
1002
+
1003
+ # Note: Auto-refresh functionality can be added with newer Gradio versions
1004
+ # For now, users can manually click the "Check for Specialist Response" button
1005
+
1006
+ # Feedback handling
1007
+ def handle_feedback(x: gr.LikeData):
1008
+ feedback_type = "πŸ‘ Helpful" if x.liked else "πŸ‘Ž Needs Improvement"
1009
+ print(f"User feedback: {feedback_type} on message {x.index}")
1010
+ # Could store this in report_data for quality improvement
1011
+
1012
+ chatbot.like(handle_feedback, None, None, like_user_message=True)
1013
+
1014
+ # Launch configuration
1015
+ if __name__ == "__main__":
1016
+ demo.launch(
1017
+ server_name="0.0.0.0",
1018
+ server_port=7860,
1019
+ show_error=True
1020
+ )