File size: 7,292 Bytes
83ff66a
c5882f3
b67fca4
77793f4
83ff66a
b0565c1
83ff66a
629d3ff
c5882f3
83ff66a
c5882f3
 
 
998c789
6ef5bdf
77793f4
83ff66a
 
c5882f3
83ff66a
998c789
b67fca4
83ff66a
629d3ff
998c789
909352f
b67fca4
629d3ff
b67fca4
 
49df0b6
 
77793f4
 
c5882f3
909352f
 
f96636d
 
 
 
909352f
 
60665db
d305e52
f0fe4ce
 
909352f
 
f0fe4ce
 
 
 
 
49df0b6
f0fe4ce
 
 
 
6091659
 
f0fe4ce
6091659
 
 
 
 
f0fe4ce
 
 
 
909352f
a0905ae
77793f4
 
909352f
 
a0905ae
909352f
a0905ae
629d3ff
a0905ae
 
 
 
629d3ff
a0905ae
 
 
 
 
 
 
 
 
 
629d3ff
a0905ae
 
 
 
 
629d3ff
 
 
a0905ae
629d3ff
909352f
998c789
a0905ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
998c789
a0905ae
 
 
909352f
a0905ae
 
 
 
629d3ff
a0905ae
 
909352f
a0905ae
629d3ff
a0905ae
 
 
 
 
 
49df0b6
909352f
49df0b6
 
629d3ff
49df0b6
c5882f3
9c4076b
998c789
ed3187b
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
155
156
157
import gradio as gr
from transformers import pipeline
from PIL import Image
import torch
import os
import spaces

# --- Initialize the Model Pipeline (No changes) ---
print("Loading MedGemma model...")
try:
    pipe = pipeline(
        "image-text-to-text",
        model="google/medgemma-4b-it",
        torch_dtype=torch.bfloat16,
        device_map="auto",
        token=os.environ.get("HF_TOKEN")
    )
    model_loaded = True
    print("Model loaded successfully!")
except Exception as e:
    model_loaded = False
    print(f"Error loading model: {e}")

# --- Core CONVERSATIONAL Logic (No changes) ---
@spaces.GPU()
def handle_conversation_turn(user_input: str, user_image: Image.Image, history: list):
    """
    Manages a single conversation turn with state management.
    """
    if not model_loaded:
        history.append((user_input, "Error: The AI model is not loaded."))
        return history, history, None

    try:
        system_prompt = (
            "You are an expert, empathetic AI medical assistant conducting a virtual consultation. "
            "Your primary goal is to ask clarifying questions to understand the user's symptoms thoroughly. "
            "Do NOT provide a diagnosis or a list of possibilities right away. Ask only one or two focused questions per turn. "
            "If the user provides an image, your first step is to analyze it from an expert perspective. Briefly describe the key findings from the image. "
            "Then, use this analysis to ask relevant follow-up questions about the user's symptoms or medical history to better understand the context. "
            "For example, after seeing a rash, you might say, 'I see a reddish rash with well-defined borders on the forearm. To help me understand more, could you tell me when you first noticed this? Is it itchy, painful, or does it have any other sensation?'"
            "After several turns of asking questions, when you feel you have gathered enough information, you must FIRST state that you are ready to provide a summary. "
            "THEN, in the SAME response, provide a list of possible conditions, your reasoning, and a clear, actionable next-steps plan."
        )

        generation_args = {"max_new_tokens": 1024, "do_sample": True, "temperature": 0.7}
        ai_response = ""
        
        if user_image:
            print("Image detected. Using multimodal 'messages' format...")
            messages = [{"role": "system", "content": [{"type": "text", "text": system_prompt}]}]
            for user_msg, assistant_msg in history:
                messages.append({"role": "user", "content": [{"type": "text", "text": user_msg}]})
                messages.append({"role": "assistant", "content": [{"type": "text", "text": assistant_msg}]})
            latest_user_content = [{"type": "text", "text": user_input}, {"type": "image", "image": user_image}]
            messages.append({"role": "user", "content": latest_user_content})
            output = pipe(text=messages, **generation_args)
            ai_response = output[0]["generated_text"][-1]["content"]
        else:
            print("No image detected. Using robust 'text-only' format.")
            prompt_parts = [f"<start_of_turn>system\n{system_prompt}"]
            for user_msg, assistant_msg in history:
                prompt_parts.append(f"<start_of_turn>user\n{user_msg}")
                prompt_parts.append(f"<start_of_turn>model\n{assistant_msg}")
            prompt_parts.append(f"<start_of_turn>user\n{user_input}")
            prompt_parts.append("<start_of_turn>model")
            prompt = "\n".join(prompt_parts)
            output = pipe(prompt, **generation_args)
            full_text = output[0]["generated_text"]
            ai_response = full_text.split("<start_of_turn>model")[-1].strip()

        history.append((user_input, ai_response))
        return history, history, None, "" # Return empty string to clear textbox

    except Exception as e:
        history.append((user_input, f"An error occurred: {str(e)}"))
        print(f"An exception occurred during conversation turn: {type(e).__name__}: {e}")
        return history, history, None, user_input # Return user_input to not clear on error

# --- UI MODIFICATION: Custom CSS for the sticky footer ---
css = """
/* Make the main app container fill the screen height and add padding at the bottom */
div.gradio-container {
    height: 100vh !important;
    padding-bottom: 120px !important; /* Space for the sticky footer */
}
/* Style the input bar to be fixed at the bottom of the screen */
#sticky-footer {
    position: fixed !important;
    bottom: 0px !important;
    left: 0px !important;
    width: 100% !important;
    background: white !important;
    padding: 10px !important;
    border-top: 1px solid #E5E7EB !important; /* Light gray border */
    z-index: 1000;
}
/* Center the clear button */
#clear-button-container {
    display: flex;
    justify-content: center;
    padding: 10px;
}
"""

# --- Gradio Interface with New Chat Layout ---
with gr.Blocks(theme=gr.themes.Soft(), title="AI Doctor Consultation", css=css) as demo:
    conversation_history = gr.State([])

    gr.HTML('<div style="text-align: center; padding: 10px;"><h1>🩺 AI Symptom Consultation</h1></div>')
    
    # The chatbot display is now the main element at the top.
    chatbot_display = gr.Chatbot(height=550, label="Consultation", show_copy_button=True)
    
    # The input controls are wrapped in a Column with a specific ID for CSS styling.
    with gr.Column(elem_id="sticky-footer"):
        with gr.Row(vertical_align="center"):
            image_input = gr.Image(label="Image", type="pil", height=100, show_label=False, container=False)
            user_textbox = gr.Textbox(
                label="Your Message", 
                placeholder="Type your message here...", 
                show_label=False,
                scale=4,
                container=False, # Important for alignment
            )
            send_button = gr.Button("Send", variant="primary", scale=1)

    # The clear button is placed just above the chatbot for easy access.
    with gr.Row(elem_id="clear-button-container"):
        clear_button = gr.Button("🗑️ Start New Consultation")

    def submit_message(user_input, user_image, history):
        # This wrapper calls the main logic.
        updated_history, new_state, cleared_image, cleared_text = handle_conversation_turn(user_input, user_image, history)
        return updated_history, new_state, cleared_image, cleared_text

    # --- Event Handlers ---
    # Handle both button click and Enter key press
    send_button.click(
        fn=submit_message,
        inputs=[user_textbox, image_input, conversation_history],
        outputs=[chatbot_display, conversation_history, image_input, user_textbox]
    )
    user_textbox.submit(
        fn=submit_message,
        inputs=[user_textbox, image_input, conversation_history],
        outputs=[chatbot_display, conversation_history, image_input, user_textbox]
    )
    
    clear_button.click(
        lambda: ([], [], None, ""), 
        outputs=[chatbot_display, conversation_history, image_input, user_textbox]
    )

if __name__ == "__main__":
    print("Starting Gradio interface...")
    demo.launch(debug=True)