|
|
import gradio as gr
|
|
|
from carebridge_client import CareBridgeTranslator
|
|
|
|
|
|
|
|
|
translator = None
|
|
|
|
|
|
def load_translator():
|
|
|
global translator
|
|
|
if translator is None:
|
|
|
translator = CareBridgeTranslator()
|
|
|
return translator
|
|
|
|
|
|
|
|
|
LANGUAGES = [
|
|
|
"English", "Polish", "Romanian", "Punjabi", "Urdu",
|
|
|
"Portuguese", "Spanish", "Arabic", "Bengali", "Gujarati", "Italian"
|
|
|
]
|
|
|
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
"""
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
with gr.Blocks(css=CUSTOM_CSS, title="SIMBOTI Live") as app:
|
|
|
|
|
|
|
|
|
mode = gr.State("text")
|
|
|
|
|
|
|
|
|
with gr.Row(elem_classes="app-header"):
|
|
|
gr.HTML("<span class='app-title'>π SIMBOTI</span>")
|
|
|
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("<span class='lang-arrow'>β</span>")
|
|
|
target_lang = gr.Dropdown(LANGUAGES, value="Polish", show_label=False, container=False, scale=0, min_width=120)
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
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])
|
|
|
|
|
|
|
|
|
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]
|
|
|
)
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
app.launch(ssr_mode=False)
|
|
|
|