File size: 6,125 Bytes
fcae81e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
043c14c
 
 
 
fcae81e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1f5310
fcae81e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1f5310
fcae81e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b7f9927
fcae81e
 
 
 
043c14c
fcae81e
 
 
 
 
 
 
 
 
9ba3438
fcae81e
 
 
 
 
5a791fc
 
fcae81e
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import gradio as gr
import httpx
import os
import json
import base64
from PIL import Image
import io
import docs  

API_KEY = os.getenv("API_KEY")  
MODAL_API_ENDPOINT = os.getenv("MODAL_API_ENDPOINT")

def encode_image_to_base64(image):
    if image is None:
        return None
    buffered = io.BytesIO()
    image.save(buffered, format="PNG")
    return base64.b64encode(buffered.getvalue()).decode()

async def call_my_api(message, history, image=None):
    # Support multimodal: message can be dict with 'text' and 'files'
    user_text = message["text"] if isinstance(message, dict) else message
    if user_text.strip().lower().startswith("(example)"):
        user_text = user_text.strip()[9:].lstrip()
    image_obj = None
    
    if image is None and isinstance(message, dict) and message.get("files"):
        # message["files"] is a list of file paths or file objects
        # For Gradio, it may be a list of PIL Images
        image_obj = message["files"][0] if message["files"] else None
    else:
        image_obj = image

    if isinstance(image_obj, str):
        image_obj = Image.open(image_obj)

    image_base64 = encode_image_to_base64(image_obj) if image_obj else None

    payload = {
        "prompt": "You are a helpful and positive health coach. Explain in simple terms to a patient or non-medical person on the following question or statement.\n\n" + user_text,
        "image": image_base64
    }

    headers = {
        "Content-Type": "application/json",
        "X-API-Key": API_KEY
    }

    try:
        async with httpx.AsyncClient() as client:
            async with client.stream('POST', MODAL_API_ENDPOINT, json=payload, headers=headers, timeout=120.0) as response:
                response.raise_for_status()
                async for line in response.aiter_lines():
                    if line.startswith('data: '):
                        try:
                            data = json.loads(line[6:])  # Remove 'data: ' prefix
                            if data['type'] == 'final':
                                yield data['content']['response']
                            elif data['type'] == 'thinking':
                                yield data['content'].get('message', '')
                            elif data['type'] == 'tool_call':
                                yield f"Using tool: {data['content'].get('name', '')}"
                            elif data['type'] == 'tool_result':
                                yield f"Tool result: {data['content'].get('result', '')}"
                        except json.JSONDecodeError:
                            continue
    except httpx.RequestError as e:
        print(f"Error calling API: {e}")
        yield f"Error: Could not connect to the API. {e}"
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        yield "An unexpected error occurred."


def vote(chatbot, history, vote):
    print(f"Vote: {vote}")
    print(f"Chatbot: {chatbot}")
    print(f"History: {history}")
    return chatbot, history

with gr.Blocks(
    theme=gr.themes.Soft(
        primary_hue="blue",
        secondary_hue="blue",
        neutral_hue="slate",
        font=["Inter", "sans-serif"],
    ),
    css_paths=["assets/custom.css"],
    title="Agent medGemma"
) as demo:
    chatbot = gr.Chatbot(
        placeholder="Ask me anything about a medical condition.",
        type="messages",
        height=600
    )
    chatbot.like(vote, inputs=[chatbot, gr.State([]), gr.State("")], outputs=[chatbot, gr.State([])])

    with gr.Row():
        with gr.Column(scale=2):
            gr.Markdown("# 🏥 Agent MedGemma", elem_id="main-title")
        with gr.Column(scale=3):
            gr.Markdown(
                "<div class='tagline'>Simple and accessible medical facts</div>",
                elem_id="main-tagline"
            )

    with gr.Row():
        with gr.Column(scale=1, elem_id="chat-col"):
            chat_interface = gr.ChatInterface(
                multimodal=False,
                fn=call_my_api,
                chatbot=chatbot,
                theme="soft",
                examples=[
                    "(example) Tell me about the causes of a heart attack.",
                    "(example) What should I do with serious vomiting?",
                    "(example) Should I take double my insulin now that I forgot to take it?",
                    "(example) What are the most common symptoms of a stroke?"
                ],
                example_icons=["assets/heartbreak.svg",
                                "assets/sickface.svg",
                                "assets/injection.svg",
                                "assets/brain.svg"]
            )

    gr.Markdown(
        """
        <div class='disclaimer-box'>
            <p> Modal backend is turned off since completion of hackathon. Host your own Modal LLM endpoint by referring to the .py files. </p>
            <p>
                <strong>Medical Disclaimer:</strong> This AI assistant is designed for educational and informational purposes only. 
                It does not constitute medical advice, diagnosis, or treatment. Always consult with qualified healthcare professionals 
                for medical decisions. This tool aims to promote health literacy and empower individuals to better understand their 
                health, but should not replace professional medical consultation. See below link for more on the technical details.
            </p>
        </div>
        """,
        elem_id="disclaimer"
    )

    gr.Markdown(
        """
        <div style='text-align: center; margin-top: 20px; padding: 10px; border-top: 1px solid #e0e0e0;'>
            <a href='https://agents-mcp-hackathon-agentic-coach-advisor-medgemma.hf.space/docs' target="_blank" rel="noopener" style='text-decoration: none; color: #666;'>📚 View Technical Documentation</a>
        </div>
        """,
        elem_id="footer"
    )

with demo.route("Technical Documentation", "/docs"):
    docs.docs_demo.render()

if __name__ == "__main__":
    demo.launch(favicon_path="assets/medical_cross_icon_144218.ico")