Spaces:
Running
on
Zero
Running
on
Zero
| 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("<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) | |
| # --- 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) | |