NurseCitizenDeveloper commited on
Commit
38193da
·
verified ·
1 Parent(s): d006165

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +179 -268
app.py CHANGED
@@ -1,18 +1,7 @@
1
  import gradio as gr
2
- import os
3
  from carebridge_client import CareBridgeTranslator
4
 
5
- # --- Asset Management ---
6
- if not os.path.exists("simboti_logo.jpg"):
7
- try:
8
- from assets import LOGO_BASE64
9
- import base64
10
- with open("simboti_logo.jpg", "wb") as f:
11
- f.write(base64.b64decode(LOGO_BASE64))
12
- except ImportError:
13
- pass # Ignore if assets.py not found
14
-
15
- # --- Initialize Client ---
16
  translator = None
17
 
18
  def load_translator():
@@ -27,303 +16,225 @@ LANGUAGES = [
27
  "Portuguese", "Spanish", "Arabic", "Bengali", "Gujarati", "Italian"
28
  ]
29
 
30
- # --- Custom CSS for Premium Design ---
31
  CUSTOM_CSS = """
32
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
33
 
34
- * {
35
- font-family: 'Inter', sans-serif !important;
36
- }
37
 
38
  .gradio-container {
39
- max-width: 1200px !important;
40
  margin: auto !important;
41
- background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%) !important;
 
42
  }
43
 
44
- .main-header {
45
- text-align: center;
46
- padding: 40px 20px;
 
 
 
 
47
  background: white;
48
- border-radius: 20px;
49
- margin-bottom: 30px;
50
- box-shadow: 0 4px 20px rgba(0,0,0,0.08);
51
  }
52
-
53
- .main-header h1 {
54
- font-size: 2.5rem;
55
  font-weight: 700;
56
- background: linear-gradient(135deg, #4F46E5 0%, #06B6D4 100%);
57
- -webkit-background-clip: text;
58
- -webkit-text-fill-color: transparent;
59
- margin-bottom: 10px;
60
  }
61
-
62
- .main-header p {
63
- color: #64748b;
64
- font-size: 1.1rem;
 
 
 
 
 
 
 
 
65
  }
 
66
 
67
- .feature-card {
 
68
  background: white;
69
  border-radius: 16px;
70
- padding: 24px;
71
- box-shadow: 0 2px 12px rgba(0,0,0,0.06);
 
 
72
  border: 1px solid #e2e8f0;
73
- transition: all 0.3s ease;
74
  }
75
-
76
- .feature-card:hover {
77
- box-shadow: 0 8px 30px rgba(0,0,0,0.12);
78
- transform: translateY(-2px);
79
- }
80
-
81
- .card-title {
82
- font-size: 1.25rem;
83
- font-weight: 600;
84
- color: #1e293b;
85
- margin-bottom: 8px;
86
- }
87
-
88
- .card-subtitle {
89
- color: #64748b;
90
- font-size: 0.9rem;
91
- }
92
-
93
- .highlight {
94
- background: linear-gradient(120deg, #fef3c7 0%, #fef3c7 100%);
95
- padding: 2px 6px;
96
- border-radius: 4px;
97
- font-weight: 600;
98
- }
99
-
100
- .patient-display {
101
- font-size: 2.5rem !important;
102
  color: #1e293b !important;
103
- font-weight: 600 !important;
104
- text-align: center !important;
105
- padding: 40px !important;
106
- background: linear-gradient(135deg, #dbeafe 0%, #e0e7ff 100%) !important;
107
- border-radius: 16px !important;
108
- border: 2px solid #6366f1 !important;
109
- min-height: 120px !important;
110
  }
111
 
112
- .primary-btn {
113
- background: linear-gradient(135deg, #4F46E5 0%, #6366f1 100%) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  border: none !important;
115
- color: white !important;
116
- padding: 14px 28px !important;
117
- font-weight: 600 !important;
118
- border-radius: 12px !important;
119
- font-size: 1rem !important;
120
- transition: all 0.3s ease !important;
121
  }
122
-
123
- .primary-btn:hover {
124
- transform: scale(1.02) !important;
125
- box-shadow: 0 8px 20px rgba(79, 70, 229, 0.4) !important;
126
  }
127
-
128
- .language-selector {
129
- background: white !important;
130
- border-radius: 12px !important;
131
- border: 2px solid #e2e8f0 !important;
132
  }
133
-
134
- .tab-nav button {
135
- font-weight: 600 !important;
136
- padding: 12px 24px !important;
137
  }
138
 
139
- .tab-nav button.selected {
140
- background: linear-gradient(135deg, #4F46E5 0%, #6366f1 100%) !important;
141
- color: white !important;
142
- }
143
 
144
- footer {
145
- display: none !important;
 
 
 
 
 
 
146
  }
147
  """
148
 
149
- # --- UI Functions ---
150
- def translate_message(text, source_lang, target_lang):
151
  if not text:
152
- return "", "", None
153
-
154
- yield "Translating...", "<div class='patient-display'>⏳ Translating...</div>", None
155
- app_translator = load_translator()
156
- result = app_translator.translate_text(text, source_lang, target_lang)
157
- tts_file = app_translator.speak_text(result, target_lang)
158
- large_display = f"<div class='patient-display'>{result}</div>"
159
- yield result, large_display, tts_file
160
 
161
  def translate_speech(audio, source_lang, target_lang):
162
  if audio is None:
163
- return "", "", None
164
-
165
- yield "Listening...", "<div class='patient-display'>🎧 Processing audio...</div>", None
166
- app_translator = load_translator()
167
- result = app_translator.translate_audio(audio, source_lang, target_lang)
168
- tts_file = app_translator.speak_text(result, target_lang)
169
- large_display = f"<div class='patient-display'>{result}</div>"
170
- yield result, large_display, tts_file
171
 
172
- def translate_leaflet(image, source_lang, target_lang):
173
  if image is None:
174
- return "", "", None
175
-
176
- yield "Scanning...", "<div class='patient-display'>📄 Scanning document...</div>", None
177
- app_translator = load_translator()
178
- result = app_translator.translate_image(image, source_lang, target_lang)
179
- tts_file = app_translator.speak_text(result, target_lang)
180
- large_display = f"<div class='patient-display'>{result}</div>"
181
- yield result, large_display, tts_file
182
-
183
- def translate_video(video, source_lang, target_lang):
184
- if video is None:
185
- return "", "", None
186
-
187
- yield "Processing...", "<div class='patient-display'>🎥 Analyzing video...</div>", None
188
- app_translator = load_translator()
189
- result = app_translator.translate_video(video, source_lang, target_lang)
190
- tts_file = app_translator.speak_text(result, target_lang)
191
- large_display = f"<div class='patient-display'>{result}</div>"
192
- yield result, large_display, tts_file
193
 
194
  # --- App Layout ---
195
  with gr.Blocks(css=CUSTOM_CSS, title="SIMBOTI Live") as app:
196
-
197
- # Header
198
- gr.HTML("""
199
- <div class="main-header">
200
- <img src="file/simboti_logo.jpg" style="width: 100px; height: 100px; border-radius: 20px; margin-bottom: 16px;" />
201
- <h1>SIMBOTI Live</h1>
202
- <p><strong>S</strong>peech <strong>I</strong>ntelligent <strong>M</strong>ultimodal <strong>B</strong>ot for <strong>O</strong>utreach <strong>T</strong>ranslation <strong>I</strong>mplementation</p>
203
- <p style="margin-top: 16px;">
204
- Built with <span class="highlight">TranslateGemma 4B</span> • Supports 11 clinical languages • Privacy-first
205
- </p>
206
- </div>
207
- """)
208
-
209
- # Language Selection
210
- with gr.Row():
211
- with gr.Column(scale=1):
212
- gr.HTML("<div class='feature-card'><div class='card-title'>🌍 Source Language</div><div class='card-subtitle'>What language are you speaking?</div></div>")
213
- source_lang = gr.Dropdown(LANGUAGES, value="English", show_label=False, container=False)
214
- with gr.Column(scale=1):
215
- gr.HTML("<div class='feature-card'><div class='card-title'>🎯 Target Language</div><div class='card-subtitle'>What language does the patient speak?</div></div>")
216
- target_lang = gr.Dropdown(LANGUAGES, value="Polish", show_label=False, container=False)
217
-
218
- gr.HTML("<br>")
219
-
220
- # Main Tabs
221
- with gr.Tabs() as tabs:
222
-
223
- # Tab 1: Text Translation
224
- with gr.TabItem("💬 Text Translation"):
225
- gr.HTML("""
226
- <div class="feature-card" style="margin-bottom: 20px;">
227
- <div class="card-title">Quick Text Translation</div>
228
- <div class="card-subtitle">Type your message and it will be translated and <span class="highlight">spoken aloud</span> for the patient.</div>
229
- </div>
230
- """)
231
- with gr.Row():
232
- with gr.Column(scale=1):
233
- clinician_text = gr.Textbox(
234
- label="Your Message",
235
- placeholder="e.g., Do you have any allergies?",
236
- lines=4
237
- )
238
- translate_btn = gr.Button("🚀 Translate & Speak", elem_classes=["primary-btn"])
239
- with gr.Column(scale=1):
240
- patient_output_html = gr.HTML(label="Patient Display")
241
- patient_audio = gr.Audio(label="📢 Audio Output", autoplay=True)
242
- patient_output_raw = gr.Textbox(visible=False)
243
-
244
- translate_btn.click(
245
- translate_message,
246
- inputs=[clinician_text, source_lang, target_lang],
247
- outputs=[patient_output_raw, patient_output_html, patient_audio]
248
- )
249
-
250
- # Tab 2: Live Speech
251
- with gr.TabItem("🎙️ Live Speech"):
252
- gr.HTML("""
253
- <div class="feature-card" style="margin-bottom: 20px;">
254
- <div class="card-title">Voice-to-Voice Translation</div>
255
- <div class="card-subtitle">Speak into the microphone and SIMBOTI will <span class="highlight">translate in real-time</span>.</div>
256
- </div>
257
- """)
258
- with gr.Row():
259
- with gr.Column(scale=1):
260
- mic_input = gr.Audio(sources=["microphone"], type="filepath", label="🎤 Record Your Voice")
261
- speech_translate_btn = gr.Button("🚀 Translate Speech", elem_classes=["primary-btn"])
262
- with gr.Column(scale=1):
263
- speech_output_html = gr.HTML(label="Patient Display")
264
- speech_audio = gr.Audio(label="📢 Translated Audio", autoplay=True)
265
- speech_output_raw = gr.Textbox(visible=False)
266
-
267
- speech_translate_btn.click(
268
- translate_speech,
269
- inputs=[mic_input, source_lang, target_lang],
270
- outputs=[speech_output_raw, speech_output_html, speech_audio]
271
- )
272
-
273
- # Tab 3: Document Scan
274
- with gr.TabItem("📄 Document Scan"):
275
- gr.HTML("""
276
- <div class="feature-card" style="margin-bottom: 20px;">
277
- <div class="card-title">Document & Leaflet Translation</div>
278
- <div class="card-subtitle">Upload a photo of medication instructions, consent forms, or any clinical document.</div>
279
- </div>
280
- """)
281
- with gr.Row():
282
- with gr.Column(scale=1):
283
- doc_image = gr.Image(type="pil", label="📷 Upload Document Photo")
284
- doc_translate_btn = gr.Button("🚀 Scan & Translate", elem_classes=["primary-btn"])
285
- with gr.Column(scale=1):
286
- doc_result_html = gr.HTML(label="Translated Text")
287
- doc_audio = gr.Audio(label="📢 Read Aloud", autoplay=False)
288
- doc_result_raw = gr.Textbox(visible=False)
289
-
290
- doc_translate_btn.click(
291
- translate_leaflet,
292
- inputs=[doc_image, source_lang, target_lang],
293
- outputs=[doc_result_raw, doc_result_html, doc_audio]
294
- )
295
-
296
- # Tab 4: Video OCR
297
- with gr.TabItem("🎥 Video Translation"):
298
- gr.HTML("""
299
- <div class="feature-card" style="margin-bottom: 20px;">
300
- <div class="card-title">Video Text Extraction</div>
301
- <div class="card-subtitle">Extract and translate text from videos - useful for <span class="highlight">instructional videos</span>.</div>
302
- </div>
303
- """)
304
- with gr.Row():
305
- with gr.Column(scale=1):
306
- video_input = gr.Video(label="🎬 Upload Video")
307
- video_translate_btn = gr.Button("🚀 Process Video", elem_classes=["primary-btn"])
308
- with gr.Column(scale=1):
309
- video_result_html = gr.HTML(label="Translated Text")
310
- video_audio = gr.Audio(label="📢 Read Aloud", autoplay=False)
311
- video_result_raw = gr.Textbox(visible=False)
312
-
313
- video_translate_btn.click(
314
- translate_video,
315
- inputs=[video_input, source_lang, target_lang],
316
- outputs=[video_result_raw, video_result_html, video_audio]
317
- )
318
 
319
- # Footer
320
- gr.HTML("""
321
- <div style="text-align: center; padding: 30px; margin-top: 40px; color: #64748b; font-size: 0.9rem;">
322
- <p>🔒 <strong>Privacy First</strong>: All translations run on-device via ZeroGPU. No data is stored.</p>
323
- <p style="margin-top: 8px;">Powered by SIMBOTI • Built with TranslateGemma 4B</p>
324
- </div>
325
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
  # Launch
328
  if __name__ == "__main__":
329
- app.launch(ssr_mode=False)
 
1
  import gradio as gr
 
2
  from carebridge_client import CareBridgeTranslator
3
 
4
+ # --- Initialize Client (Lazy) ---
 
 
 
 
 
 
 
 
 
 
5
  translator = None
6
 
7
  def load_translator():
 
16
  "Portuguese", "Spanish", "Arabic", "Bengali", "Gujarati", "Italian"
17
  ]
18
 
19
+ # --- Minimal CSS (Gemini Dictation Inspired) ---
20
  CUSTOM_CSS = """
21
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
22
 
23
+ * { font-family: 'Inter', sans-serif !important; }
 
 
24
 
25
  .gradio-container {
26
+ max-width: 900px !important;
27
  margin: auto !important;
28
+ background: #f8fafc !important;
29
+ min-height: 100vh !important;
30
  }
31
 
32
+ /* --- Header --- */
33
+ .app-header {
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: space-between;
37
+ padding: 16px 24px;
38
+ border-bottom: 1px solid #e2e8f0;
39
  background: white;
 
 
 
40
  }
41
+ .app-title {
42
+ font-size: 1.25rem;
 
43
  font-weight: 700;
44
+ color: #1e293b;
 
 
 
45
  }
46
+ .lang-pills {
47
+ display: flex;
48
+ gap: 8px;
49
+ align-items: center;
50
+ }
51
+ .lang-pill {
52
+ background: #e0e7ff;
53
+ color: #4338ca;
54
+ padding: 6px 14px;
55
+ border-radius: 20px;
56
+ font-size: 0.85rem;
57
+ font-weight: 500;
58
  }
59
+ .lang-arrow { color: #94a3b8; font-size: 1.2rem; }
60
 
61
+ /* --- Document Area --- */
62
+ .document-area {
63
  background: white;
64
  border-radius: 16px;
65
+ margin: 24px;
66
+ padding: 32px;
67
+ min-height: 400px;
68
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
69
  border: 1px solid #e2e8f0;
 
70
  }
71
+ .output-text {
72
+ font-size: 1.75rem !important;
73
+ line-height: 1.6 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  color: #1e293b !important;
75
+ min-height: 200px !important;
76
+ text-align: center;
77
+ padding: 40px 20px;
78
+ }
79
+ .placeholder-text {
80
+ color: #94a3b8;
81
+ font-style: italic;
82
  }
83
 
84
+ /* --- Floating Controls --- */
85
+ .floating-bar {
86
+ position: fixed;
87
+ bottom: 24px;
88
+ left: 50%;
89
+ transform: translateX(-50%);
90
+ display: flex;
91
+ gap: 16px;
92
+ align-items: center;
93
+ background: white;
94
+ padding: 12px 24px;
95
+ border-radius: 40px;
96
+ box-shadow: 0 4px 20px rgba(0,0,0,0.15);
97
+ z-index: 100;
98
+ }
99
+ .fab-btn {
100
+ width: 56px !important;
101
+ height: 56px !important;
102
+ border-radius: 50% !important;
103
+ font-size: 1.5rem !important;
104
+ display: flex !important;
105
+ align-items: center !important;
106
+ justify-content: center !important;
107
  border: none !important;
108
+ cursor: pointer !important;
109
+ transition: transform 0.2s, box-shadow 0.2s !important;
 
 
 
 
110
  }
111
+ .fab-btn:hover {
112
+ transform: scale(1.1) !important;
113
+ box-shadow: 0 4px 12px rgba(0,0,0,0.2) !important;
 
114
  }
115
+ .fab-primary {
116
+ background: linear-gradient(135deg, #4F46E5, #6366f1) !important;
117
+ color: white !important;
118
+ width: 72px !important;
119
+ height: 72px !important;
120
  }
121
+ .fab-secondary {
122
+ background: #f1f5f9 !important;
123
+ color: #475569 !important;
 
124
  }
125
 
126
+ /* Hide Gradio footer */
127
+ footer { display: none !important; }
 
 
128
 
129
+ /* Input modals */
130
+ .input-modal {
131
+ background: white;
132
+ border-radius: 16px;
133
+ padding: 24px;
134
+ margin: 0 24px 100px 24px;
135
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
136
+ border: 1px solid #e2e8f0;
137
  }
138
  """
139
 
140
+ # --- Translation Functions ---
141
+ def translate_text(text, source_lang, target_lang):
142
  if not text:
143
+ return "Enter text above to translate..."
144
+ yield "⏳ Translating..."
145
+ t = load_translator()
146
+ result = t.translate_text(text, source_lang, target_lang)
147
+ yield result
 
 
 
148
 
149
  def translate_speech(audio, source_lang, target_lang):
150
  if audio is None:
151
+ return "Record audio using the microphone..."
152
+ yield "🎧 Processing speech..."
153
+ t = load_translator()
154
+ result = t.translate_audio(audio, source_lang, target_lang)
155
+ yield result
 
 
 
156
 
157
+ def translate_document(image, source_lang, target_lang):
158
  if image is None:
159
+ return "Upload a document image..."
160
+ yield "📄 Scanning document..."
161
+ t = load_translator()
162
+ result = t.translate_image(image, source_lang, target_lang)
163
+ yield result
164
+
165
+ def get_tts(text, lang):
166
+ if not text or text.startswith("⏳") or text.startswith("🎧") or text.startswith("📄"):
167
+ return None
168
+ t = load_translator()
169
+ return t.speak_text(text, lang)
 
 
 
 
 
 
 
 
170
 
171
  # --- App Layout ---
172
  with gr.Blocks(css=CUSTOM_CSS, title="SIMBOTI Live") as app:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
+ # State for current mode
175
+ mode = gr.State("text")
176
+
177
+ # --- Header ---
178
+ with gr.Row(elem_classes="app-header"):
179
+ gr.HTML("<span class='app-title'>🌐 SIMBOTI</span>")
180
+ with gr.Row(elem_classes="lang-pills"):
181
+ source_lang = gr.Dropdown(LANGUAGES, value="English", show_label=False, container=False, scale=0, min_width=120)
182
+ gr.HTML("<span class='lang-arrow'>→</span>")
183
+ target_lang = gr.Dropdown(LANGUAGES, value="Polish", show_label=False, container=False, scale=0, min_width=120)
184
+
185
+ # --- Document Output Area ---
186
+ with gr.Column(elem_classes="document-area"):
187
+ output_display = gr.Textbox(
188
+ value="Your translation will appear here...",
189
+ show_label=False,
190
+ interactive=False,
191
+ lines=8,
192
+ elem_classes="output-text"
193
+ )
194
+ audio_output = gr.Audio(label="Listen", autoplay=True, visible=True)
195
+
196
+ # --- Input Section (Toggleable) ---
197
+ with gr.Column(elem_classes="input-modal", visible=True) as text_input_section:
198
+ text_input = gr.Textbox(label="Type your message", placeholder="e.g., Where does it hurt?", lines=2)
199
+ text_submit = gr.Button("Translate", variant="primary")
200
+
201
+ with gr.Column(elem_classes="input-modal", visible=False) as audio_input_section:
202
+ audio_input = gr.Audio(sources=["microphone"], type="filepath", label="🎤 Record")
203
+ audio_submit = gr.Button("Translate Speech", variant="primary")
204
+
205
+ with gr.Column(elem_classes="input-modal", visible=False) as doc_input_section:
206
+ doc_input = gr.Image(type="pil", label="📷 Upload Document")
207
+ doc_submit = gr.Button("Scan & Translate", variant="primary")
208
+
209
+ # --- Mode Switchers ---
210
+ with gr.Row(elem_classes="floating-bar"):
211
+ text_mode_btn = gr.Button("⌨️", elem_classes="fab-btn fab-secondary")
212
+ mic_mode_btn = gr.Button("🎤", elem_classes="fab-btn fab-primary")
213
+ doc_mode_btn = gr.Button("📎", elem_classes="fab-btn fab-secondary")
214
+
215
+ # --- Mode Toggle Logic ---
216
+ def show_text_mode():
217
+ return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
218
+ def show_audio_mode():
219
+ return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
220
+ def show_doc_mode():
221
+ return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
222
+
223
+ text_mode_btn.click(show_text_mode, outputs=[text_input_section, audio_input_section, doc_input_section])
224
+ mic_mode_btn.click(show_audio_mode, outputs=[text_input_section, audio_input_section, doc_input_section])
225
+ doc_mode_btn.click(show_doc_mode, outputs=[text_input_section, audio_input_section, doc_input_section])
226
+
227
+ # --- Translation Triggers ---
228
+ text_submit.click(translate_text, inputs=[text_input, source_lang, target_lang], outputs=[output_display]).then(
229
+ get_tts, inputs=[output_display, target_lang], outputs=[audio_output]
230
+ )
231
+ audio_submit.click(translate_speech, inputs=[audio_input, source_lang, target_lang], outputs=[output_display]).then(
232
+ get_tts, inputs=[output_display, target_lang], outputs=[audio_output]
233
+ )
234
+ doc_submit.click(translate_document, inputs=[doc_input, source_lang, target_lang], outputs=[output_display]).then(
235
+ get_tts, inputs=[output_display, target_lang], outputs=[audio_output]
236
+ )
237
 
238
  # Launch
239
  if __name__ == "__main__":
240
+ app.launch(ssr_mode=False)