Spaces:
Running on Zero
Running on Zero
Upload app.py
Browse files
app.py
CHANGED
|
@@ -121,16 +121,25 @@ def system_instruction():
|
|
| 121 |
"4. Never recommend or rank treatments. If the patient asks for medical advice, kindly "
|
| 122 |
"redirect them to discuss it with their care team, then continue with a follow-up question.\n"
|
| 123 |
"5. Tone: empathetic, respectful, never alarming.\n"
|
| 124 |
-
"6.
|
| 125 |
-
"
|
| 126 |
-
"
|
| 127 |
-
"
|
| 128 |
-
"
|
| 129 |
-
"
|
| 130 |
-
"
|
| 131 |
-
"
|
| 132 |
-
"
|
| 133 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
"7. When you judge that you have enough to summarize their priorities (or after several "
|
| 135 |
"exchanges), instead of asking another question, output a final summary. Begin that final "
|
| 136 |
"message with the exact token <<SUMMARY>> on its own line, then 4-7 short bullet points "
|
|
@@ -197,18 +206,18 @@ def start_chat(api_key, *radio_values):
|
|
| 197 |
flags = detect_tensions(answers)
|
| 198 |
flag_text = "\n".join(f"- {f}" for f in flags) if flags else "- (No obvious contradictions; explore their highest-stakes priorities.)"
|
| 199 |
|
| 200 |
-
|
|
|
|
|
|
|
| 201 |
"Here is the patient's completed profile (16 yes/no answers):\n\n"
|
| 202 |
f"{profile}\n\n"
|
| 203 |
"FLAGGED TENSIONS / PRIORITIES TO EXPLORE:\n"
|
| 204 |
f"{flag_text}\n\n"
|
| 205 |
-
"
|
| 206 |
-
"
|
| 207 |
-
"and then ask your first follow-up question.\n\n"
|
| 208 |
-
f'Patient: "{FIRST_QUESTION}"'
|
| 209 |
)
|
| 210 |
|
| 211 |
-
convo = [{"role": "user", "content":
|
| 212 |
try:
|
| 213 |
reply = gemini_reply(client, convo, 0)
|
| 214 |
except Exception as e:
|
|
@@ -216,16 +225,12 @@ def start_chat(api_key, *radio_values):
|
|
| 216 |
return (gr.update(), [], [], 0, gr.update(visible=True), gr.update(visible=False))
|
| 217 |
|
| 218 |
convo.append({"role": "assistant", "content": reply})
|
| 219 |
-
|
| 220 |
-
chat_display = [
|
| 221 |
-
{"role": "user", "content": FIRST_QUESTION},
|
| 222 |
-
{"role": "assistant", "content": reply},
|
| 223 |
-
]
|
| 224 |
return (
|
| 225 |
chat_display, # chatbot
|
| 226 |
chat_display, # display_state
|
| 227 |
convo, # convo_state
|
| 228 |
-
|
| 229 |
gr.update(visible=False), # hide intake
|
| 230 |
gr.update(visible=True), # show chat
|
| 231 |
)
|
|
@@ -378,6 +383,33 @@ button.secondary, .gradio-container button.secondary {
|
|
| 378 |
color: #111111 !important;
|
| 379 |
}
|
| 380 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
footer {visibility: hidden;}
|
| 382 |
"""
|
| 383 |
|
|
@@ -393,9 +425,8 @@ with gr.Blocks(title="P3 Myeloma Follow-up Chatbot", css=CSS,
|
|
| 393 |
"<p id='app-subtitle'>Follow-up Questions for Shared Decision Making</p>"
|
| 394 |
)
|
| 395 |
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
placeholder="Paste your key here, or leave blank to use the Space secret")
|
| 399 |
|
| 400 |
convo_state = gr.State([])
|
| 401 |
display_state = gr.State([])
|
|
@@ -414,8 +445,14 @@ with gr.Blocks(title="P3 Myeloma Follow-up Chatbot", css=CSS,
|
|
| 414 |
# ---- Chat panel ----
|
| 415 |
with gr.Group(visible=False) as chat_panel:
|
| 416 |
chatbot = gr.Chatbot(label="Follow-up Conversation", type="messages", height=460)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
with gr.Row():
|
| 418 |
-
msg = gr.Textbox(placeholder="Type your
|
| 419 |
send_btn = gr.Button("Send", variant="primary", scale=1)
|
| 420 |
restart_btn = gr.Button("Start Over")
|
| 421 |
|
|
|
|
| 121 |
"4. Never recommend or rank treatments. If the patient asks for medical advice, kindly "
|
| 122 |
"redirect them to discuss it with their care team, then continue with a follow-up question.\n"
|
| 123 |
"5. Tone: empathetic, respectful, never alarming.\n"
|
| 124 |
+
"6. TREATMENT-OPTIONS QUESTION: When the patient asks what the best treatment options are for "
|
| 125 |
+
"their myeloma (e.g. 'Using my profile, what are the best treatment options for my myeloma?'), "
|
| 126 |
+
"your answer must be grounded STRICTLY in the 16 yes/no profile answers provided to you. Do "
|
| 127 |
+
"this:\n"
|
| 128 |
+
" - Open with one warm sentence.\n"
|
| 129 |
+
" - Then walk through how THEIR specific stated priorities map to the kinds of treatment "
|
| 130 |
+
"approaches commonly discussed for relapsed/refractory myeloma. Tie each point explicitly back "
|
| 131 |
+
"to their answers (for example: a preference for taking medications at home and the fewest pills "
|
| 132 |
+
"points toward asking the team about convenient outpatient or oral regimens; openness to clinical "
|
| 133 |
+
"trials means trials are worth raising; willingness to endure side effects and wanting an "
|
| 134 |
+
"aggressive approach points toward asking about more intensive options; prioritizing quality of "
|
| 135 |
+
"life points toward asking about regimens that protect daily functioning; living alone with "
|
| 136 |
+
"limited support points toward asking about logistics and monitoring).\n"
|
| 137 |
+
" - Do NOT invent clinical details, drug names beyond broad well-known categories, dosing, or "
|
| 138 |
+
"anything not implied by their answers. Do NOT rank, prescribe, or state which option is "
|
| 139 |
+
"medically best. Frame everything as 'based on what you told us, here are options and questions "
|
| 140 |
+
"to raise with your care team.'\n"
|
| 141 |
+
" - End by reminding them their care team must confirm what is medically appropriate, then ask "
|
| 142 |
+
"your first single follow-up question drawn from their profile and the flagged tensions.\n"
|
| 143 |
"7. When you judge that you have enough to summarize their priorities (or after several "
|
| 144 |
"exchanges), instead of asking another question, output a final summary. Begin that final "
|
| 145 |
"message with the exact token <<SUMMARY>> on its own line, then 4-7 short bullet points "
|
|
|
|
| 206 |
flags = detect_tensions(answers)
|
| 207 |
flag_text = "\n".join(f"- {f}" for f in flags) if flags else "- (No obvious contradictions; explore their highest-stakes priorities.)"
|
| 208 |
|
| 209 |
+
# Seed the conversation with the profile as the first user turn so every later
|
| 210 |
+
# turn (including when the user pastes the sample question) can be grounded in it.
|
| 211 |
+
seed = (
|
| 212 |
"Here is the patient's completed profile (16 yes/no answers):\n\n"
|
| 213 |
f"{profile}\n\n"
|
| 214 |
"FLAGGED TENSIONS / PRIORITIES TO EXPLORE:\n"
|
| 215 |
f"{flag_text}\n\n"
|
| 216 |
+
"Acknowledge in one short sentence that you have their profile. Do NOT ask a question yet "
|
| 217 |
+
"and do NOT list treatment options yet. Simply invite them to ask their question."
|
|
|
|
|
|
|
| 218 |
)
|
| 219 |
|
| 220 |
+
convo = [{"role": "user", "content": seed}]
|
| 221 |
try:
|
| 222 |
reply = gemini_reply(client, convo, 0)
|
| 223 |
except Exception as e:
|
|
|
|
| 225 |
return (gr.update(), [], [], 0, gr.update(visible=True), gr.update(visible=False))
|
| 226 |
|
| 227 |
convo.append({"role": "assistant", "content": reply})
|
| 228 |
+
chat_display = [{"role": "assistant", "content": reply}]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
return (
|
| 230 |
chat_display, # chatbot
|
| 231 |
chat_display, # display_state
|
| 232 |
convo, # convo_state
|
| 233 |
+
0, # followup count (no follow-up asked yet)
|
| 234 |
gr.update(visible=False), # hide intake
|
| 235 |
gr.update(visible=True), # show chat
|
| 236 |
)
|
|
|
|
| 383 |
color: #111111 !important;
|
| 384 |
}
|
| 385 |
|
| 386 |
+
/* ---- Sample question hint ---- */
|
| 387 |
+
.sample-q {
|
| 388 |
+
background: #f7f7f7 !important;
|
| 389 |
+
border: 1px solid #e2e2e2 !important;
|
| 390 |
+
border-left: 3px solid #7a0c2e !important;
|
| 391 |
+
border-radius: 6px !important;
|
| 392 |
+
padding: 10px 14px !important;
|
| 393 |
+
margin: 12px 0 4px 0 !important;
|
| 394 |
+
}
|
| 395 |
+
.sample-q-label {
|
| 396 |
+
display: block;
|
| 397 |
+
font-size: 0.85rem;
|
| 398 |
+
font-weight: 600;
|
| 399 |
+
color: #5a5a5a !important;
|
| 400 |
+
margin-bottom: 4px;
|
| 401 |
+
}
|
| 402 |
+
.sample-q-text {
|
| 403 |
+
display: block;
|
| 404 |
+
font-size: 0.95rem;
|
| 405 |
+
color: #111111 !important;
|
| 406 |
+
background: #ffffff !important;
|
| 407 |
+
border: 1px solid #e2e2e2 !important;
|
| 408 |
+
border-radius: 4px !important;
|
| 409 |
+
padding: 6px 10px !important;
|
| 410 |
+
font-family: 'Inter', sans-serif !important;
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
footer {visibility: hidden;}
|
| 414 |
"""
|
| 415 |
|
|
|
|
| 425 |
"<p id='app-subtitle'>Follow-up Questions for Shared Decision Making</p>"
|
| 426 |
)
|
| 427 |
|
| 428 |
+
# API key is read from the Space secret GEMINI_API_KEY (hidden, not user-facing).
|
| 429 |
+
api_key = gr.State("")
|
|
|
|
| 430 |
|
| 431 |
convo_state = gr.State([])
|
| 432 |
display_state = gr.State([])
|
|
|
|
| 445 |
# ---- Chat panel ----
|
| 446 |
with gr.Group(visible=False) as chat_panel:
|
| 447 |
chatbot = gr.Chatbot(label="Follow-up Conversation", type="messages", height=460)
|
| 448 |
+
gr.HTML(
|
| 449 |
+
"<div class='sample-q'>"
|
| 450 |
+
"<span class='sample-q-label'>Sample question — copy and paste it into the box below to begin:</span>"
|
| 451 |
+
f"<code class='sample-q-text'>{FIRST_QUESTION}</code>"
|
| 452 |
+
"</div>"
|
| 453 |
+
)
|
| 454 |
with gr.Row():
|
| 455 |
+
msg = gr.Textbox(placeholder="Type or paste your question here...", show_label=False, scale=8)
|
| 456 |
send_btn = gr.Button("Send", variant="primary", scale=1)
|
| 457 |
restart_btn = gr.Button("Start Over")
|
| 458 |
|