import gradio as gr
from carebridge_client import CareBridgeTranslator
# --- Initialize Client (Lazy) ---
translator = None
def load_translator():
global translator
if translator is None:
translator = CareBridgeTranslator()
return translator
# --- Languages ---
LANGUAGES = [
"English", "Polish", "Romanian", "Punjabi", "Urdu",
"Portuguese", "Spanish", "Arabic", "Bengali", "Gujarati", "Italian"
]
# --- Minimal CSS (Gemini Dictation Inspired) ---
CUSTOM_CSS = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* { font-family: 'Inter', sans-serif !important; }
.gradio-container {
max-width: 900px !important;
margin: auto !important;
background: #f8fafc !important;
min-height: 100vh !important;
}
/* --- Header --- */
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
border-bottom: 1px solid #e2e8f0;
background: white;
}
.app-title {
font-size: 1.25rem;
font-weight: 700;
color: #1e293b;
}
.lang-pills {
display: flex;
gap: 8px;
align-items: center;
}
.lang-pill {
background: #e0e7ff;
color: #4338ca;
padding: 6px 14px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
}
.lang-arrow { color: #94a3b8; font-size: 1.2rem; }
/* --- Document Area --- */
.document-area {
background: white;
border-radius: 16px;
margin: 24px;
padding: 32px;
min-height: 400px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border: 1px solid #e2e8f0;
}
.output-text {
font-size: 1.75rem !important;
line-height: 1.6 !important;
color: #1e293b !important;
min-height: 200px !important;
text-align: center;
padding: 40px 20px;
}
.placeholder-text {
color: #94a3b8;
font-style: italic;
}
/* --- Floating Controls --- */
.floating-bar {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 16px;
align-items: center;
background: white;
padding: 12px 24px;
border-radius: 40px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
z-index: 100;
}
.fab-btn {
width: 56px !important;
height: 56px !important;
border-radius: 50% !important;
font-size: 1.5rem !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
border: none !important;
cursor: pointer !important;
transition: transform 0.2s, box-shadow 0.2s !important;
}
.fab-btn:hover {
transform: scale(1.1) !important;
box-shadow: 0 4px 12px rgba(0,0,0,0.2) !important;
}
.fab-primary {
background: linear-gradient(135deg, #4F46E5, #6366f1) !important;
color: white !important;
width: 72px !important;
height: 72px !important;
}
.fab-secondary {
background: #f1f5f9 !important;
color: #475569 !important;
}
/* Hide Gradio footer */
footer { display: none !important; }
/* Input modals */
.input-modal {
background: white;
border-radius: 16px;
padding: 24px;
margin: 0 24px 100px 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border: 1px solid #e2e8f0;
}
"""
# --- Translation Functions ---
def translate_text(text, source_lang, target_lang):
if not text:
return "Enter text above to translate..."
yield "⏳ Translating..."
t = load_translator()
result = t.translate_text(text, source_lang, target_lang)
yield result
def translate_speech(audio, source_lang, target_lang):
if audio is None:
return "Record audio using the microphone..."
yield "🎧 Processing speech..."
t = load_translator()
result = t.translate_audio(audio, source_lang, target_lang)
yield result
def translate_document(image, source_lang, target_lang):
if image is None:
return "Upload a document image..."
yield "📄 Scanning document..."
t = load_translator()
result = t.translate_image(image, source_lang, target_lang)
yield result
def get_tts(text, lang):
if not text or text.startswith("⏳") or text.startswith("🎧") or text.startswith("📄"):
return None
t = load_translator()
return t.speak_text(text, lang)
# --- App Layout ---
with gr.Blocks(css=CUSTOM_CSS, title="SIMBOTI Live") as app:
# State for current mode
mode = gr.State("text")
# --- Header ---
with gr.Row(elem_classes="app-header"):
gr.HTML("🌐 SIMBOTI")
with gr.Row(elem_classes="lang-pills"):
source_lang = gr.Dropdown(LANGUAGES, value="English", show_label=False, container=False, scale=0, min_width=120)
gr.HTML("→")
target_lang = gr.Dropdown(LANGUAGES, value="Polish", show_label=False, container=False, scale=0, min_width=120)
# --- Document Output Area ---
with gr.Column(elem_classes="document-area"):
output_display = gr.Textbox(
value="Your translation will appear here...",
show_label=False,
interactive=False,
lines=8,
elem_classes="output-text"
)
audio_output = gr.Audio(label="Listen", autoplay=True, visible=True)
# --- Input Section (Toggleable) ---
with gr.Column(elem_classes="input-modal", visible=True) as text_input_section:
text_input = gr.Textbox(label="Type your message", placeholder="e.g., Where does it hurt?", lines=2)
text_submit = gr.Button("Translate", variant="primary")
with gr.Column(elem_classes="input-modal", visible=False) as audio_input_section:
audio_input = gr.Audio(sources=["microphone"], type="filepath", label="🎤 Record")
audio_submit = gr.Button("Translate Speech", variant="primary")
with gr.Column(elem_classes="input-modal", visible=False) as doc_input_section:
doc_input = gr.Image(type="pil", label="📷 Upload Document")
doc_submit = gr.Button("Scan & Translate", variant="primary")
# --- Mode Switchers ---
with gr.Row(elem_classes="floating-bar"):
text_mode_btn = gr.Button("⌨️", elem_classes="fab-btn fab-secondary")
mic_mode_btn = gr.Button("🎤", elem_classes="fab-btn fab-primary")
doc_mode_btn = gr.Button("📎", elem_classes="fab-btn fab-secondary")
# --- Mode Toggle Logic ---
def show_text_mode():
return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
def show_audio_mode():
return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
def show_doc_mode():
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
text_mode_btn.click(show_text_mode, outputs=[text_input_section, audio_input_section, doc_input_section])
mic_mode_btn.click(show_audio_mode, outputs=[text_input_section, audio_input_section, doc_input_section])
doc_mode_btn.click(show_doc_mode, outputs=[text_input_section, audio_input_section, doc_input_section])
# --- Translation Triggers ---
text_submit.click(translate_text, inputs=[text_input, source_lang, target_lang], outputs=[output_display]).then(
get_tts, inputs=[output_display, target_lang], outputs=[audio_output]
)
audio_submit.click(translate_speech, inputs=[audio_input, source_lang, target_lang], outputs=[output_display]).then(
get_tts, inputs=[output_display, target_lang], outputs=[audio_output]
)
doc_submit.click(translate_document, inputs=[doc_input, source_lang, target_lang], outputs=[output_display]).then(
get_tts, inputs=[output_display, target_lang], outputs=[audio_output]
)
# Launch
if __name__ == "__main__":
app.launch(ssr_mode=False)