DIrtyCha commited on
Commit
235ced4
·
1 Parent(s): e5a20e3

Initial deployment: Medical AI Semantic Translator

Browse files
Files changed (3) hide show
  1. .gitignore +10 -0
  2. app.py +260 -0
  3. requirements.txt +3 -0
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Local development files
2
+ *.pyc
3
+ __pycache__/
4
+ .env
5
+ *.log
6
+ .DS_Store
7
+
8
+ # Never commit API keys!
9
+ *.key
10
+ secrets.txt
app.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
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
10
+ import json
11
+ 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-turbo-preview" # Use newer model name
26
+
27
+ # ============================================================================
28
+ # IMPORTS AND SETUP
29
+ # ============================================================================
30
+
31
+ try:
32
+ from openai import OpenAI
33
+ client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
34
+ except ImportError:
35
+ print("ERROR: OpenAI library not installed.")
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:
60
+ transcript = client.audio.transcriptions.create(
61
+ model="whisper-1",
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(
94
+ model=OPENAI_MODEL,
95
+ messages=[
96
+ {"role": "system", "content": MEDICAL_ANTHROPOLOGIST_PROMPT},
97
+ {"role": "user", "content": f"Patient transcript:\n\n{transcription}"}
98
+ ],
99
+ response_format={"type": "json_object"},
100
+ temperature=0.7
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
+
159
+ if "Error" in trans_status or not transcription.strip():
160
+ return trans_status, transcription, "<div style='padding: 20px; color: #ff6b6b;'>⚠️ Cannot analyze without transcription.</div>"
161
+
162
+ formatted_html, json_output = analyze_with_llm(transcription)
163
+
164
+ if "Error" in formatted_html:
165
+ return "❌ Analysis failed", transcription, formatted_html
166
+
167
+ return "✅ Analysis complete", transcription, formatted_html
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
+
259
+ app = create_ui()
260
+ app.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio==4.44.0
2
+ openai>=1.0.0
3
+ numpy