from dotenv import load_dotenv load_dotenv() import os import gradio as gr from brain_of_the_doctor import encode_image, analyze_image_with_query from voice_of_the_patient import transcribe_with_groq from voice_of_the_doctor import text_to_speech_with_gtts, text_to_speech_with_elevenlabs system_prompt = """You have to act as a professional doctor, i know you are not but this is for learning purpose. What's in this image?. Do you find anything wrong with it medically? If you make a differential, suggest some remedies for them. Donot add any numbers or special characters in your response. Your response should be in one long paragraph. Also always answer as if you are answering to a real person. Donot say 'In the image I see' but say 'With what I see, I think you have ....' Dont respond as an AI model in markdown, your answer should mimic that of an actual doctor not an AI bot, Keep your answer concise (max 2 sentences). No preamble, start your answer right away please""" def process_inputs(audio_filepath, image_filepath): speech_to_text_output = "" if audio_filepath: try: speech_to_text_output = transcribe_with_groq( GROQ_API_KEY=os.environ.get("GROQ_API_KEY"), audio_filepath=audio_filepath, stt_model="whisper-large-v3" ) except Exception as e: speech_to_text_output = f"Error transcribing audio: {str(e)}" if image_filepath: try: doctor_response = analyze_image_with_query( query=system_prompt + " " + speech_to_text_output, encoded_image=encode_image(image_filepath), model="meta-llama/llama-4-scout-17b-16e-instruct" ) except Exception as e: doctor_response = f"Error analyzing image: {str(e)}" else: doctor_response = "No image provided for me to analyze." output_filepath = "final.mp3" try: elevenlabs_key = os.environ.get("ELEVENLABS_API_KEY") if elevenlabs_key: text_to_speech_with_elevenlabs(input_text=doctor_response, output_filepath=output_filepath) else: text_to_speech_with_gtts(input_text=doctor_response, output_filepath=output_filepath) except Exception as e: print(f"ElevenLabs TTS failed, falling back to gTTS: {e}") try: text_to_speech_with_gtts(input_text=doctor_response, output_filepath=output_filepath) except Exception as e2: print(f"gTTS also failed: {e2}") output_filepath = None return speech_to_text_output, doctor_response, output_filepath # white/grey backgrounds from every Gradio wrapper div. # ───────────────────────────────────────────────────────────────────────────── ANIMATED_BG = """ """ # ── CSS ─────────────────────────────────────────────────────────────────────── custom_css = """ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700&family=Exo+2:wght@300;400;500;600&display=swap'); :root { --panel-bg: rgba(8, 26, 52, 0.74); --border-soft: rgba(90, 180, 220, 0.22); --text-main: #ddf0fa; --text-muted: #7ab8d4; } /* Ensure dark base at CSS level too */ html { background: #07111e !important; } body { background: #07111e !important; } gradio-app, gradio-app > div, .gradio-container, .gradio-container > div, .main, .contain, .app, #component-0, .tabs, .tabitem, .form, .wrap, .gap { background: transparent !important; background-color: transparent !important; } body, gradio-app { font-family: 'Exo 2', sans-serif !important; color: var(--text-main) !important; } /* Title */ h1 { font-family: 'Orbitron', monospace !important; font-weight: 700 !important; font-size: clamp(1.3rem, 2.6vw, 2rem) !important; letter-spacing: 0.10em !important; text-align: center !important; color: #8dd8f0 !important; padding: 1.4rem 0 0.2rem !important; } h1::after { content: ''; display: block; margin: 0.45rem auto 0; width: 200px; height: 1.5px; background: linear-gradient(90deg, transparent, #5ab4dc, #4ecdc4, transparent); border-radius: 2px; } /* Cards */ .block, .gr-block, .gr-box, .gr-panel, .panel { background: var(--panel-bg) !important; border: 1px solid var(--border-soft) !important; border-radius: 12px !important; backdrop-filter: blur(20px) saturate(1.4) !important; -webkit-backdrop-filter: blur(20px) saturate(1.4) !important; box-shadow: 0 4px 28px rgba(0,0,0,0.55) !important; transition: border-color 0.3s !important; } .block:hover { border-color: rgba(90,180,220,0.42) !important; } /* Labels */ label span, .gr-label, label { font-family: 'Exo 2', sans-serif !important; font-size: 0.76rem !important; font-weight: 600 !important; letter-spacing: 0.08em !important; color: #7ecfe8 !important; text-transform: uppercase !important; } /* Inputs */ textarea, input[type="text"] { background: rgba(4,16,36,0.82) !important; border: 1px solid rgba(90,180,220,0.20) !important; border-radius: 8px !important; color: var(--text-main) !important; font-family: 'Exo 2', sans-serif !important; font-size: 0.90rem !important; transition: border-color 0.2s !important; } textarea:focus, input[type="text"]:focus { border-color: rgba(90,180,220,0.50) !important; outline: none !important; } /* Primary button */ button.primary, .gr-button-primary, button[variant="primary"] { font-family: 'Orbitron', monospace !important; font-weight: 600 !important; font-size: 0.73rem !important; letter-spacing: 0.09em !important; text-transform: uppercase !important; background: linear-gradient(135deg, #0d4a6e 0%, #1a7aab 100%) !important; color: #ddf0fa !important; border: 1px solid rgba(90,180,220,0.36) !important; border-radius: 8px !important; padding: 0.6rem 1.5rem !important; transition: background 0.25s, transform 0.15s !important; } button.primary:hover { background: linear-gradient(135deg, #115880, #2192cc) !important; transform: translateY(-1px) !important; } button.secondary, .gr-button-secondary { font-family: 'Exo 2', sans-serif !important; font-size: 0.77rem !important; background: rgba(90,180,220,0.07) !important; color: #7ecfe8 !important; border: 1px solid rgba(90,180,220,0.22) !important; border-radius: 8px !important; transition: background 0.2s !important; } button.secondary:hover { background: rgba(90,180,220,0.16) !important; } /* Audio */ .waveform-container, audio { background: rgba(4,16,36,0.65) !important; border-radius: 10px !important; border: 1px solid rgba(90,180,220,0.16) !important; } /* Image upload */ .image-container, .upload-container { border: 1.5px dashed rgba(90,180,220,0.25) !important; border-radius: 10px !important; background: rgba(4,16,36,0.50) !important; transition: border-color 0.3s !important; } .image-container:hover { border-color: rgba(90,180,220,0.50) !important; } ::-webkit-scrollbar { width: 5px; } ::-webkit-scrollbar-track { background: rgba(0,16,32,0.4); } ::-webkit-scrollbar-thumb { background: #2a6e96; border-radius: 3px; } footer { display: none !important; } """ # ── Gradio theme ────────────────────────────────────────────────────────────── theme = gr.themes.Base( primary_hue=gr.themes.colors.cyan, secondary_hue=gr.themes.colors.blue, neutral_hue=gr.themes.colors.slate, font=[gr.themes.GoogleFont("Exo 2"), "sans-serif"], font_mono=[gr.themes.GoogleFont("Orbitron"), "monospace"], ).set( body_background_fill="transparent", body_background_fill_dark="transparent", block_background_fill="rgba(8,26,52,0.74)", block_background_fill_dark="rgba(8,26,52,0.74)", block_border_color="rgba(90,180,220,0.22)", block_border_color_dark="rgba(90,180,220,0.22)", block_label_text_color="#7ecfe8", block_label_text_color_dark="#7ecfe8", input_background_fill="rgba(4,16,36,0.82)", input_background_fill_dark="rgba(4,16,36,0.82)", input_border_color="rgba(90,180,220,0.20)", input_border_color_dark="rgba(90,180,220,0.20)", button_primary_background_fill="linear-gradient(135deg,#0d4a6e,#1a7aab)", button_primary_background_fill_dark="linear-gradient(135deg,#0d4a6e,#1a7aab)", button_primary_text_color="#ddf0fa", button_primary_text_color_dark="#ddf0fa", ) # ── UI ──────────────────────────────────────────────────────────────────────── with gr.Blocks(theme=theme, css=custom_css, title="AI Doctor — Vision & Voice") as iface: gr.HTML(ANIMATED_BG) gr.Markdown("""

⚕ Healio-AI Doctor Vision & Voice

Upload a medical image and describe your symptoms via microphone.
The AI doctor will analyze and respond with a diagnosis.

""") # Cute animated AI doctor robot in the bottom-right corner gr.HTML(""" """) with gr.Row(): with gr.Column(scale=1): audio_input = gr.Audio(sources=["microphone"], type="filepath", label="🎙 Patient Audio Input") image_input = gr.Image(type="filepath", label="🩻 Medical Image Upload") with gr.Row(): submit_btn = gr.Button("🩺 Analyze", variant="primary") clear_btn = gr.Button("🗑 Clear", variant="secondary") with gr.Column(scale=1): text_out = gr.Textbox(label="📝 Speech Transcription", lines=2) doc_out = gr.Textbox(label="🩺 Doctor's Diagnosis", lines=5) audio_out = gr.Audio(label="🔊 Doctor's Voice Response", type="filepath") submit_btn.click( fn=process_inputs, inputs=[audio_input, image_input], outputs=[text_out, doc_out, audio_out], ) clear_btn.click( fn=lambda: (None, None, "", "", None), inputs=[], outputs=[audio_input, image_input, text_out, doc_out, audio_out], ) iface.launch(server_name="0.0.0.0",server_port=7860, allowed_paths=["."])