DIrtyCha commited on
Commit
9baf3f7
·
1 Parent(s): e2403dd
Files changed (2) hide show
  1. .gitattributes +10 -0
  2. app.py +181 -111
.gitattributes CHANGED
@@ -33,3 +33,13 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ # Local development files
37
+ *.pyc
38
+ __pycache__/
39
+ .env
40
+ *.log
41
+ .DS_Store
42
+
43
+ # Never commit API keys!
44
+ *.key
45
+ secrets.txt
app.py CHANGED
@@ -2,8 +2,6 @@
2
  Module 1: Cross-Cultural Semantic Translator MVP
3
  =================================================
4
  A medical AI platform for translating cultural pain metaphors into structured medical ontologies.
5
-
6
- Deployed on Hugging Face Spaces.
7
  """
8
 
9
  import gradio as gr
@@ -12,20 +10,15 @@ import os
12
  from typing import Dict, Tuple, Optional
13
 
14
  # ============================================================================
15
- # CONFIGURATION - 从环境变量读取
16
  # ============================================================================
17
 
18
- # SECURITY: NEVER hardcode API keys in public repos
19
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
20
-
21
- # Transcription mode: Force API mode on Hugging Face (no GPU access)
22
  TRANSCRIPTION_MODE = "api"
23
-
24
- # OpenAI model for analysis
25
- OPENAI_MODEL = "gpt-4.1" # Use newer model name
26
 
27
  # ============================================================================
28
- # IMPORTS AND SETUP
29
  # ============================================================================
30
 
31
  try:
@@ -36,24 +29,21 @@ except ImportError:
36
  client = None
37
 
38
  # ============================================================================
39
- # SYSTEM PROMPT FOR LLM
40
  # ============================================================================
41
 
42
  MEDICAL_ANTHROPOLOGIST_PROMPT = """You are an expert Medical Anthropologist. Your goal is to translate cultural pain metaphors into structured medical ontologies. Do NOT act as a doctor making a final diagnosis. Analyze the patient's transcript and output a strict JSON object with these exact keys: 'literal_translation', 'metaphor_mapping', 'mcgill_pain_ontology', 'psychological_and_stoicism_flags', 'physician_action_note'. Make sure to include English and original language in metaphor_mapping for reference."""
43
 
44
  # ============================================================================
45
- # AUDIO TRANSCRIPTION FUNCTION
46
  # ============================================================================
47
 
48
  def transcribe_audio(audio_path: Optional[str]) -> Tuple[str, str]:
49
- """
50
- Transcribe audio using OpenAI Whisper API.
51
- """
52
  if audio_path is None:
53
- return "", "⚠️ No audio recorded. Please record audio first."
54
 
55
  if client is None:
56
- return "", "❌ OpenAI client not initialized. API key missing."
57
 
58
  try:
59
  with open(audio_path, "rb") as audio_file:
@@ -62,32 +52,17 @@ def transcribe_audio(audio_path: Optional[str]) -> Tuple[str, str]:
62
  file=audio_file,
63
  response_format="text"
64
  )
65
- transcription = transcript.strip()
66
- status = f"✓ Transcribed via OpenAI Whisper API"
67
-
68
- if not transcription:
69
- return "", "⚠️ Transcription is empty. Please check your audio quality."
70
-
71
- return transcription, status
72
-
73
  except Exception as e:
74
- error_msg = f"❌ Transcription error: {str(e)}"
75
- print(error_msg)
76
- return "", error_msg
77
 
78
  # ============================================================================
79
- # LLM ANALYSIS FUNCTION
80
  # ============================================================================
81
 
82
  def analyze_with_llm(transcription: str) -> Tuple[str, str]:
83
- """
84
- Send transcription to OpenAI API for medical anthropological analysis.
85
- """
86
- if not transcription or transcription.strip() == "":
87
- return "<div style='padding: 20px; color: #ffc107;'>⚠️ No transcription to analyze.</div>", "{}"
88
-
89
- if client is None:
90
- return "<div style='padding: 20px; color: #ff6b6b;'>❌ OpenAI client not initialized. Please set OPENAI_API_KEY in Space secrets.</div>", "{}"
91
 
92
  try:
93
  response = client.chat.completions.create(
@@ -101,58 +76,178 @@ def analyze_with_llm(transcription: str) -> Tuple[str, str]:
101
  )
102
 
103
  json_text = response.choices[0].message.content
104
-
105
- if not json_text or json_text.strip() == "":
106
- return "<div style='padding: 20px; color: #ff6b6b;'>❌ Empty response from LLM</div>", "{}"
107
-
108
- try:
109
- parsed_json = json.loads(json_text)
110
- except json.JSONDecodeError as je:
111
- error_html = f"""
112
- <div style='padding: 20px; background-color: #f8d7da; border-left: 5px solid #dc3545; border-radius: 8px;'>
113
- <h3 style='color: #721c24;'>⚠️ JSON Parse Error</h3>
114
- <p style='color: #721c24;'>{str(je)}</p>
115
- </div>
116
- """
117
- return error_html, json_text
118
-
119
  formatted_output = format_json_for_display(parsed_json)
 
120
  return formatted_output, json_text
121
 
122
  except Exception as e:
123
  import traceback
124
- error_details = traceback.format_exc()
125
  error_html = f"""
126
  <div style='padding: 20px; background-color: #f8d7da; border-left: 5px solid #dc3545; border-radius: 8px;'>
127
- <h3 style='color: #721c24;'>❌ LLM Analysis Error</h3>
128
- <pre style='color: #721c24; font-size: 12px; overflow-x: auto;'>{error_details}</pre>
129
  </div>
130
  """
131
  return error_html, "{}"
132
 
133
  # ============================================================================
134
- # JSON FORMATTING FOR DISPLAY
135
  # ============================================================================
136
 
137
  def format_json_for_display(data: Dict) -> str:
138
- """Format the JSON output into a human-readable medical report."""
139
- # [保留你原来的 format_json_for_display 函数的完整代码]
140
- # 这里为了简洁省略,实际部署时复制完整函数
141
  html_parts = ['''
142
- <div style="font-family: 'Segoe UI', Arial, sans-serif; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: #ffffff; box-shadow: 0 10px 25px rgba(0,0,0,0.2); line-height: 1.8;">
 
 
 
 
 
 
 
 
143
  ''']
144
 
145
- # [完整的格式化逻辑...]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
  html_parts.append('</div>')
148
  return ''.join(html_parts)
149
 
150
  # ============================================================================
151
- # MAIN PROCESSING FUNCTION
152
  # ============================================================================
153
 
154
  def process_patient_audio(audio) -> Tuple[str, str, str]:
155
- """Main processing pipeline"""
156
  try:
157
  transcription, trans_status = transcribe_audio(audio)
158
 
@@ -168,91 +263,66 @@ def process_patient_audio(audio) -> Tuple[str, str, str]:
168
 
169
  except Exception as e:
170
  import traceback
171
- error_details = traceback.format_exc()
172
  error_html = f"""
173
  <div style='padding: 20px; background-color: #f8d7da; border-left: 5px solid #dc3545; border-radius: 8px;'>
174
  <h3 style='color: #721c24;'>❌ Unexpected Error</h3>
175
- <pre style='color: #721c24; font-size: 12px; overflow-x: auto;'>{error_details}</pre>
176
  </div>
177
  """
178
  return "❌ Processing error", "Error during processing", error_html
179
 
180
  # ============================================================================
181
- # GRADIO UI SETUP
182
  # ============================================================================
183
 
184
  def create_ui():
185
- """Create the Gradio interface"""
186
-
187
  with gr.Blocks(title="Medical AI Semantic Translator", theme=gr.themes.Soft()) as app:
188
- gr.Markdown(
189
- """
190
- # 🏥 Module 1: Cross-Cultural Semantic Translator
191
- ### Translating Cultural Pain Metaphors into Medical Ontologies
192
-
193
- **Instructions:** Record your audio description of pain symptoms, then click Analyze.
194
-
195
- ⚠️ **Note:** This demo uses OpenAI's API. The Space owner must configure API keys.
196
- """
197
- )
198
 
199
  status_output = gr.Textbox(label="Status", interactive=False, lines=1)
200
 
201
  with gr.Row():
202
  with gr.Column(scale=1):
203
  gr.Markdown("### 🎤 Audio Input")
204
- audio_input = gr.Audio(
205
- sources=["microphone"],
206
- type="filepath",
207
- label="Record Your Pain Description"
208
- )
209
  submit_btn = gr.Button("🔍 Analyze", variant="primary", size="lg")
210
 
211
  gr.Markdown("### 📄 Transcription")
212
- transcription_output = gr.Textbox(
213
- label="Whisper Transcription Output",
214
- interactive=False,
215
- lines=8
216
- )
217
 
218
  with gr.Column(scale=1):
219
  gr.Markdown("### 🤖 AI Medical Anthropologist Analysis")
220
- analysis_output = gr.HTML(
221
- label="Structured Medical Ontology",
222
- value='<div style="padding: 20px; text-align: center; color: #6c757d;">Analysis results will appear here...</div>'
223
- )
224
 
225
- gr.Markdown(
226
- f"""
227
- ---
228
- **Configuration:** Transcription: `API` | LLM Model: `{OPENAI_MODEL}`
229
-
230
- **Deployed on:** [Hugging Face Spaces](https://huggingface.co/spaces/DIrtyCha/Module1demo)
231
- """
232
- )
233
 
234
- submit_btn.click(
235
- fn=process_patient_audio,
236
- inputs=[audio_input],
237
- outputs=[status_output, transcription_output, analysis_output]
238
- )
239
 
240
  return app
241
 
242
  # ============================================================================
243
- # MAIN ENTRY POINT
244
  # ============================================================================
245
 
246
  if __name__ == "__main__":
247
- print("=" * 70)
248
- print("🚀 Medical AI Semantic Translator MVP - Hugging Face Spaces")
249
  print("=" * 70)
250
 
251
  if not OPENAI_API_KEY:
252
- print("⚠️ WARNING: OPENAI_API_KEY not set in Space secrets!")
253
- print(" Go to: Settings → Repository Secrets → Add OPENAI_API_KEY")
254
  else:
255
- print("✅ OpenAI API key loaded from environment")
256
 
257
  print("=" * 70)
258
 
 
2
  Module 1: Cross-Cultural Semantic Translator MVP
3
  =================================================
4
  A medical AI platform for translating cultural pain metaphors into structured medical ontologies.
 
 
5
  """
6
 
7
  import gradio as gr
 
10
  from typing import Dict, Tuple, Optional
11
 
12
  # ============================================================================
13
+ # CONFIGURATION
14
  # ============================================================================
15
 
 
16
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
 
 
17
  TRANSCRIPTION_MODE = "api"
18
+ OPENAI_MODEL = "gpt-4-turbo-preview"
 
 
19
 
20
  # ============================================================================
21
+ # SETUP
22
  # ============================================================================
23
 
24
  try:
 
29
  client = None
30
 
31
  # ============================================================================
32
+ # SYSTEM PROMPT
33
  # ============================================================================
34
 
35
  MEDICAL_ANTHROPOLOGIST_PROMPT = """You are an expert Medical Anthropologist. Your goal is to translate cultural pain metaphors into structured medical ontologies. Do NOT act as a doctor making a final diagnosis. Analyze the patient's transcript and output a strict JSON object with these exact keys: 'literal_translation', 'metaphor_mapping', 'mcgill_pain_ontology', 'psychological_and_stoicism_flags', 'physician_action_note'. Make sure to include English and original language in metaphor_mapping for reference."""
36
 
37
  # ============================================================================
38
+ # TRANSCRIPTION
39
  # ============================================================================
40
 
41
  def transcribe_audio(audio_path: Optional[str]) -> Tuple[str, str]:
 
 
 
42
  if audio_path is None:
43
+ return "", "⚠️ No audio recorded."
44
 
45
  if client is None:
46
+ return "", "❌ OpenAI client not initialized."
47
 
48
  try:
49
  with open(audio_path, "rb") as audio_file:
 
52
  file=audio_file,
53
  response_format="text"
54
  )
55
+ return transcript.strip(), "✓ Transcribed via OpenAI Whisper API"
 
 
 
 
 
 
 
56
  except Exception as e:
57
+ return "", f"❌ Transcription error: {str(e)}"
 
 
58
 
59
  # ============================================================================
60
+ # LLM ANALYSIS
61
  # ============================================================================
62
 
63
  def analyze_with_llm(transcription: str) -> Tuple[str, str]:
64
+ if not transcription or not client:
65
+ return "<div style='padding: 20px; color: #ff6b6b;'>❌ Cannot analyze</div>", "{}"
 
 
 
 
 
 
66
 
67
  try:
68
  response = client.chat.completions.create(
 
76
  )
77
 
78
  json_text = response.choices[0].message.content
79
+ parsed_json = json.loads(json_text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  formatted_output = format_json_for_display(parsed_json)
81
+
82
  return formatted_output, json_text
83
 
84
  except Exception as e:
85
  import traceback
 
86
  error_html = f"""
87
  <div style='padding: 20px; background-color: #f8d7da; border-left: 5px solid #dc3545; border-radius: 8px;'>
88
+ <h3 style='color: #721c24;'>❌ Error</h3>
89
+ <pre style='color: #721c24; font-size: 12px; overflow-x: auto;'>{traceback.format_exc()}</pre>
90
  </div>
91
  """
92
  return error_html, "{}"
93
 
94
  # ============================================================================
95
+ # JSON FORMATTING - 完整版本从 semantic_translator_mvp.py 复制
96
  # ============================================================================
97
 
98
  def format_json_for_display(data: Dict) -> str:
99
+ """Format JSON into human-readable medical report"""
100
+
 
101
  html_parts = ['''
102
+ <div style="
103
+ font-family: 'Segoe UI', Arial, sans-serif;
104
+ padding: 30px;
105
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
106
+ border-radius: 15px;
107
+ color: #ffffff;
108
+ box-shadow: 0 10px 25px rgba(0,0,0,0.2);
109
+ line-height: 1.8;
110
+ ">
111
  ''']
112
 
113
+ # Debug section
114
+ import json
115
+ raw_json = json.dumps(data, indent=2, ensure_ascii=False)
116
+ html_parts.append(f'''
117
+ <details style="margin-bottom: 20px; padding: 15px; background-color: rgba(0, 0, 0, 0.2); border-radius: 8px;">
118
+ <summary style="cursor: pointer; font-weight: bold; color: #ffd700;">🔍 Debug: Raw JSON</summary>
119
+ <pre style="margin-top: 10px; padding: 10px; background-color: rgba(0, 0, 0, 0.3); border-radius: 5px; overflow-x: auto; font-size: 12px; color: #e0e0e0;">{raw_json}</pre>
120
+ </details>
121
+ ''')
122
+
123
+ # 1. Literal Translation
124
+ if 'literal_translation' in data:
125
+ html_parts.append(f'''
126
+ <div style="margin-bottom: 25px; padding: 20px; background-color: rgba(255,255,255,0.15); border-left: 5px solid #ffd700; border-radius: 10px;">
127
+ <h2 style="margin: 0 0 15px 0; color: #ffd700; font-size: 22px; font-weight: 700;">📝 Patient's Description</h2>
128
+ <p style="margin: 0; font-size: 16px; color: #ffffff; font-style: italic;">"{data['literal_translation']}"</p>
129
+ </div>
130
+ ''')
131
+
132
+ # 2. Metaphor Mapping
133
+ if 'metaphor_mapping' in data:
134
+ metaphor = data['metaphor_mapping']
135
+ html_parts.append('''
136
+ <div style="margin-bottom: 25px; padding: 20px; background-color: rgba(255,255,255,0.15); border-left: 5px solid #4fc3f7; border-radius: 10px;">
137
+ <h2 style="margin: 0 0 15px 0; color: #4fc3f7; font-size: 22px; font-weight: 700;">🔗 Cultural Context</h2>
138
+ ''')
139
+
140
+ def render_value(val, indent=0):
141
+ margin_left = indent * 20
142
+ if isinstance(val, dict):
143
+ items = []
144
+ for k, v in val.items():
145
+ k_readable = k.replace('_', ' ').title()
146
+ items.append(f'<div style="margin: 8px 0 8px {margin_left}px;"><strong style="color: #81d4fa;">{k_readable}:</strong>{render_value(v, indent+1)}</div>')
147
+ return ''.join(items)
148
+ elif isinstance(val, list):
149
+ if not val:
150
+ return '<span style="margin-left: 10px; color: #e0e0e0;">None</span>'
151
+ items_html = '<ul style="margin: 5px 0; padding-left: 20px; color: #e0e0e0;">'
152
+ for item in val:
153
+ items_html += f'<li style="margin: 5px 0;">{render_value(item, indent) if isinstance(item, (dict, list)) else str(item)}</li>'
154
+ items_html += '</ul>'
155
+ return items_html
156
+ else:
157
+ return f'<span style="margin-left: 10px; font-size: 15px; color: #ffffff;">{str(val)}</span>'
158
+
159
+ html_parts.append(render_value(metaphor))
160
+ html_parts.append('</div>')
161
+
162
+ # 3. McGill Pain Ontology
163
+ if 'mcgill_pain_ontology' in data:
164
+ mcgill = data['mcgill_pain_ontology']
165
+ html_parts.append('''
166
+ <div style="margin-bottom: 25px; padding: 20px; background-color: rgba(255,255,255,0.15); border-left: 5px solid #ff6b6b; border-radius: 10px;">
167
+ <h2 style="margin: 0 0 15px 0; color: #ff6b6b; font-size: 22px; font-weight: 700;">🏥 McGill Pain Assessment</h2>
168
+ ''')
169
+
170
+ field_icons = {
171
+ 'location': '📍',
172
+ 'temporal_pattern': '⏱️',
173
+ 'intensity': '📊',
174
+ 'quality_descriptors': '💭',
175
+ 'associated_symptoms_to_query': '🔍',
176
+ 'functional_impact_to_query': '🚶',
177
+ 'pain_or_sensory_type': '🩺'
178
+ }
179
+
180
+ def render_mcgill(val, indent=1):
181
+ margin_left = indent * 20
182
+ if isinstance(val, dict):
183
+ items = []
184
+ for k, v in val.items():
185
+ k_readable = k.replace('_', ' ').title()
186
+ items.append(f'<div style="margin: 5px 0 5px {margin_left}px;"><em style="color: #ffd4d4;">{k_readable}:</em>{render_mcgill(v, indent+1)}</div>')
187
+ return ''.join(items)
188
+ elif isinstance(val, list):
189
+ if not val:
190
+ return '<span style="margin-left: 10px; color: #e0e0e0;">None specified</span>'
191
+ return '<span style="margin-left: 10px; color: #ffffff;">' + ', '.join(str(v) for v in val) + '</span>'
192
+ else:
193
+ return f'<span style="margin-left: 10px; color: #ffffff;">{str(val)}</span>'
194
+
195
+ if isinstance(mcgill, list):
196
+ for item in mcgill:
197
+ if isinstance(item, dict):
198
+ for key, value in item.items():
199
+ key_readable = key.replace('_', ' ').title()
200
+ icon = field_icons.get(key, '•')
201
+ html_parts.append(f'<div style="margin-bottom: 15px; padding: 12px; background-color: rgba(255,255,255,0.1); border-radius: 8px;"><strong style="color: #ffcccb; font-size: 16px;">{icon} {key_readable}:</strong>{render_mcgill(value)}</div>')
202
+ else:
203
+ html_parts.append(f'<div style="margin-bottom: 15px; padding: 12px; background-color: rgba(255,255,255,0.1); border-radius: 8px;"><p style="margin: 0; font-size: 15px; color: #ffffff;">{str(item)}</p></div>')
204
+ elif isinstance(mcgill, dict):
205
+ for key, value in mcgill.items():
206
+ key_readable = key.replace('_', ' ').title()
207
+ icon = field_icons.get(key, '•')
208
+ html_parts.append(f'<div style="margin-bottom: 15px; padding: 12px; background-color: rgba(255,255,255,0.1); border-radius: 8px;"><strong style="color: #ffcccb; font-size: 16px;">{icon} {key_readable}:</strong>{render_mcgill(value)}</div>')
209
+ else:
210
+ html_parts.append(f'<div style="margin-bottom: 15px; padding: 12px; background-color: rgba(255,255,255,0.1); border-radius: 8px;"><p style="margin: 0; font-size: 15px; color: #ffffff;">{str(mcgill)}</p></div>')
211
+
212
+ html_parts.append('</div>')
213
+
214
+ # 4. Psychological Flags
215
+ if 'psychological_and_stoicism_flags' in data:
216
+ psych = data['psychological_and_stoicism_flags']
217
+ html_parts.append('''
218
+ <div style="margin-bottom: 25px; padding: 20px; background-color: rgba(255,255,255,0.15); border-left: 5px solid #9c27b0; border-radius: 10px;">
219
+ <h2 style="margin: 0 0 15px 0; color: #ce93d8; font-size: 22px; font-weight: 700;">🧠 Psychological Assessment</h2>
220
+ ''')
221
+
222
+ for key, value in psych.items():
223
+ key_readable = key.replace('_', ' ').title()
224
+ if isinstance(value, dict):
225
+ html_parts.append(f'<p style="margin: 10px 0; font-size: 15px;"><strong style="color: #ce93d8;">{key_readable}:</strong></p>')
226
+ for sub_key, sub_value in value.items():
227
+ sub_key_readable = sub_key.replace('_', ' ').title()
228
+ html_parts.append(f'<p style="margin: 5px 0 5px 20px; font-size: 14px; color: #e0e0e0;">• {sub_key_readable}: {sub_value}</p>')
229
+ else:
230
+ html_parts.append(f'<p style="margin: 10px 0; font-size: 15px;"><strong style="color: #ce93d8;">{key_readable}:</strong> <span style="color: #ffffff;">{value}</span></p>')
231
+
232
+ html_parts.append('</div>')
233
+
234
+ # 5. Physician Action Note
235
+ if 'physician_action_note' in data:
236
+ html_parts.append(f'''
237
+ <div style="padding: 20px; background-color: rgba(255,255,255,0.2); border: 3px solid #4caf50; border-radius: 10px;">
238
+ <h2 style="margin: 0 0 15px 0; color: #a5d6a7; font-size: 22px; font-weight: 700;">⚕️ Clinical Recommendations</h2>
239
+ <p style="margin: 0; font-size: 16px; color: #ffffff; line-height: 1.9;">{data['physician_action_note']}</p>
240
+ </div>
241
+ ''')
242
 
243
  html_parts.append('</div>')
244
  return ''.join(html_parts)
245
 
246
  # ============================================================================
247
+ # MAIN PROCESSING
248
  # ============================================================================
249
 
250
  def process_patient_audio(audio) -> Tuple[str, str, str]:
 
251
  try:
252
  transcription, trans_status = transcribe_audio(audio)
253
 
 
263
 
264
  except Exception as e:
265
  import traceback
 
266
  error_html = f"""
267
  <div style='padding: 20px; background-color: #f8d7da; border-left: 5px solid #dc3545; border-radius: 8px;'>
268
  <h3 style='color: #721c24;'>❌ Unexpected Error</h3>
269
+ <pre style='color: #721c24; font-size: 12px; overflow-x: auto;'>{traceback.format_exc()}</pre>
270
  </div>
271
  """
272
  return "❌ Processing error", "Error during processing", error_html
273
 
274
  # ============================================================================
275
+ # GRADIO UI
276
  # ============================================================================
277
 
278
  def create_ui():
 
 
279
  with gr.Blocks(title="Medical AI Semantic Translator", theme=gr.themes.Soft()) as app:
280
+ gr.Markdown("""
281
+ # 🏥 Module 1: Cross-Cultural Semantic Translator
282
+ ### Translating Cultural Pain Metaphors into Medical Ontologies
283
+
284
+ **Instructions:** Record your audio description, then click Analyze.
285
+ """)
 
 
 
 
286
 
287
  status_output = gr.Textbox(label="Status", interactive=False, lines=1)
288
 
289
  with gr.Row():
290
  with gr.Column(scale=1):
291
  gr.Markdown("### 🎤 Audio Input")
292
+ audio_input = gr.Audio(sources=["microphone"], type="filepath", label="Record Your Pain Description")
 
 
 
 
293
  submit_btn = gr.Button("🔍 Analyze", variant="primary", size="lg")
294
 
295
  gr.Markdown("### 📄 Transcription")
296
+ transcription_output = gr.Textbox(label="Whisper Transcription", interactive=False, lines=8)
 
 
 
 
297
 
298
  with gr.Column(scale=1):
299
  gr.Markdown("### 🤖 AI Medical Anthropologist Analysis")
300
+ analysis_output = gr.HTML(value='<div style="padding: 20px; text-align: center; color: #6c757d;">Analysis results will appear here...</div>')
 
 
 
301
 
302
+ gr.Markdown(f"""
303
+ ---
304
+ **Configuration:** `API` mode | `{OPENAI_MODEL}`
305
+ **Deployed on:** [Hugging Face Spaces](https://huggingface.co/spaces/DIrtyCha/Module1demo)
306
+ """)
 
 
 
307
 
308
+ submit_btn.click(fn=process_patient_audio, inputs=[audio_input], outputs=[status_output, transcription_output, analysis_output])
 
 
 
 
309
 
310
  return app
311
 
312
  # ============================================================================
313
+ # MAIN
314
  # ============================================================================
315
 
316
  if __name__ == "__main__":
317
+ print("=" * 70)
318
+ print("🚀 Medical AI Semantic Translator MVP")
319
  print("=" * 70)
320
 
321
  if not OPENAI_API_KEY:
322
+ print("⚠️ WARNING: OPENAI_API_KEY not set!")
323
+ print(" Go to Settings → Repository Secrets")
324
  else:
325
+ print("✅ OpenAI API key loaded")
326
 
327
  print("=" * 70)
328