File size: 7,707 Bytes
83ff66a
c5882f3
b67fca4
77793f4
83ff66a
b0565c1
83ff66a
629d3ff
c5882f3
83ff66a
c5882f3
 
 
998c789
6ef5bdf
77793f4
83ff66a
 
c5882f3
83ff66a
998c789
b67fca4
83ff66a
b3f457b
998c789
909352f
b67fca4
b3f457b
 
b67fca4
 
b3f457b
 
 
77793f4
 
c5882f3
909352f
 
f96636d
 
 
 
909352f
 
60665db
d305e52
f0fe4ce
 
909352f
 
b3f457b
f0fe4ce
3d0f6df
f0fe4ce
b3f457b
49df0b6
f0fe4ce
 
 
 
b3f457b
6091659
3d0f6df
6091659
b3f457b
6091659
 
 
f0fe4ce
 
 
 
3d0f6df
 
 
 
b3f457b
77793f4
 
3d0f6df
 
909352f
b3f457b
909352f
b3f457b
629d3ff
b3f457b
 
 
 
 
 
 
 
 
 
629d3ff
b3f457b
 
 
 
 
629d3ff
b3f457b
 
629d3ff
 
 
909352f
998c789
b3f457b
 
 
 
3d0f6df
b3f457b
a0905ae
b3f457b
a0905ae
b3f457b
 
a0905ae
 
998c789
b3f457b
a0905ae
909352f
b3f457b
 
 
 
 
629d3ff
b3f457b
 
 
 
 
 
 
 
a0905ae
909352f
b3f457b
629d3ff
b3f457b
 
 
a0905ae
b3f457b
a0905ae
b3f457b
 
 
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
158
159
160
161
162
163
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 (Modified for Streaming) ---
@spaces.GPU()
def handle_conversation_turn(user_input: str, user_image: Image.Image, history: list):
    """
    Manages a single conversation turn and streams the AI response back.
    This function is now a Python generator.
    """
    if not model_loaded:
        history[-1] = (user_input, "Error: The AI model is not loaded.")
        yield history, history, None
        return

    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:
            # ... (logic remains the same)
            messages = [{"role": "system", "content": [{"type": "text", "text": system_prompt}]}]
            for user_msg, assistant_msg in history[:-1]:
                messages.append({"role": "user", "content": [{"type": "text", "text": user_msg}]})
                if assistant_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:
            # ... (logic remains the same)
            prompt_parts = [f"<start_of_turn>system\n{system_prompt}"]
            for user_msg, assistant_msg in history[:-1]:
                prompt_parts.append(f"<start_of_turn>user\n{user_msg}")
                if assistant_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()

        # Stream the AI response back to the chatbot
        history[-1] = (user_input, "")
        for character in ai_response:
            history[-1] = (user_input, history[-1][1] + character)
            yield history, history, None

    except Exception as e:
        error_message = f"An error occurred: {str(e)}"
        history[-1] = (user_input, error_message)
        print(f"An exception occurred during conversation turn: {type(e).__name__}: {e}")
        yield history, history, None

# --- UI MODIFICATION: Professional CSS for the chat interface ---
css = """
/* Make the main app container fill the screen height */
div.gradio-container { height: 100vh !important; }
/* Main chat area styling */
#chat-container { flex-grow: 1; overflow-y: auto; padding-bottom: 120px; }
/* Sticky footer for the input bar */
#footer-container {
    position: fixed !important; bottom: 0; left: 0; width: 100%;
    background-color: #e0f2fe !important; /* Light Sky Blue background */
    border-top: 1px solid #bae6fd !important;
    padding: 10px; z-index: 1000;
}
/* White, rounded textbox */
#user-textbox textarea {
    background-color: #ffffff !important;
    border: 1px solid #cbd5e1 !important;
    border-radius: 8px !important;
}
/* Style the image upload button */
#image-upload-btn { border: 1px dashed #9ca3af !important; border-radius: 8px !important; }
"""

with gr.Blocks(theme=gr.themes.Soft(), title="AI Doctor Consultation", css=css) as demo:
    conversation_history = gr.State([])

    with gr.Column(elem_id="chat-container"):
        chatbot_display = gr.Chatbot(label="Consultation", show_copy_button=True, bubble_full_width=False)

    with gr.Column(elem_id="footer-container"):
        with gr.Row():
            image_input = gr.Image(elem_id="image-upload-btn", label="Image", type="pil", height=80, show_label=False, container=False, scale=1)
            user_textbox = gr.Textbox(
                elem_id="user-textbox",
                label="Your Message", 
                placeholder="Type your message, or upload an image...", 
                show_label=False, scale=4, container=False
            )
            send_button = gr.Button("Send", variant="primary", scale=1)

    with gr.Row():
        clear_button = gr.Button("๐Ÿ—‘๏ธ Start New Consultation")

    # This new function handles the full UX flow: instant feedback + streaming AI response
    def submit_message_and_stream(user_input: str, user_image: Image.Image, history: list):
        if not user_input.strip() and user_image is None:
            # Do nothing if the input is empty
            return history, history, None

        # 1. Instantly add the user's message to the chat UI
        history.append((user_input, None))
        yield history, history, None
        
        # 2. Start the generator to get the AI's response stream
        for updated_history, new_state, cleared_image in handle_conversation_turn(user_input, user_image, history):
            yield updated_history, new_state, cleared_image
            
    # --- Event Handlers ---
    send_button.click(
        fn=submit_message_and_stream,
        inputs=[user_textbox, image_input, conversation_history],
        outputs=[chatbot_display, conversation_history, image_input],
    ).then(lambda: "", outputs=user_textbox) # Clear textbox after submission
    
    user_textbox.submit(
        fn=submit_message_and_stream,
        inputs=[user_textbox, image_input, conversation_history],
        outputs=[chatbot_display, conversation_history, image_input],
    ).then(lambda: "", outputs=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)