Spaces:
Running
Running
feat: improve conversation readability
Browse files
app.py
CHANGED
|
@@ -163,83 +163,142 @@ def build_recap_html(hadm_id: str, model: str, cefr: str, personality: str, reca
|
|
| 163 |
)
|
| 164 |
|
| 165 |
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
| 168 |
safe_val = html.escape(str(val)) if val not in (None, "") else "N/A"
|
| 169 |
safe_label = html.escape(str(label))
|
| 170 |
return (
|
| 171 |
-
f"<
|
| 172 |
-
f"
|
| 173 |
-
f"<
|
| 174 |
-
f"</
|
|
|
|
|
|
|
| 175 |
)
|
| 176 |
|
| 177 |
|
| 178 |
-
def
|
|
|
|
| 179 |
return (
|
| 180 |
-
f"<div style='
|
| 181 |
-
f"
|
| 182 |
-
f"
|
| 183 |
-
f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
)
|
| 185 |
|
| 186 |
|
| 187 |
def build_profile_html(p: dict) -> str:
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
f"
|
| 195 |
-
f"<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
)
|
| 197 |
-
social =
|
| 198 |
-
"
|
| 199 |
-
|
| 200 |
-
+
|
| 201 |
-
+
|
| 202 |
-
+
|
| 203 |
-
+
|
| 204 |
-
+
|
| 205 |
-
+
|
| 206 |
-
+
|
| 207 |
-
|
| 208 |
)
|
| 209 |
-
history =
|
| 210 |
-
"
|
| 211 |
-
|
| 212 |
-
+
|
| 213 |
-
+
|
| 214 |
-
|
| 215 |
)
|
| 216 |
-
visit =
|
| 217 |
-
"
|
| 218 |
-
|
| 219 |
-
+
|
| 220 |
-
+
|
| 221 |
-
+
|
| 222 |
-
+
|
| 223 |
-
+
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
)
|
| 226 |
-
return f"<div style='font-family:Noto Sans KR,Noto Sans,Malgun Gothic,Apple SD Gothic Neo,sans-serif;font-size:14px;line-height:1.5;background:#ffffff;padding:16px;border-radius:10px'>{basic}{social}{history}{visit}</div>"
|
| 227 |
|
| 228 |
|
| 229 |
def build_blind_profile_html(p: dict) -> str:
|
| 230 |
"""Show only basic demographic info for practice mode without full case details."""
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
f"
|
| 237 |
-
f"{
|
| 238 |
-
f"
|
| 239 |
-
f"
|
| 240 |
-
f"<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
)
|
| 242 |
-
return f"<div style='font-family:Noto Sans KR,Noto Sans,Malgun Gothic,Apple SD Gothic Neo,sans-serif;font-size:14px;line-height:1.5;background:#ffffff;padding:16px;border-radius:10px'>{content}</div>"
|
| 243 |
|
| 244 |
|
| 245 |
# ---------------------------------------------------------------------------
|
|
@@ -1088,7 +1147,7 @@ with gr.Blocks(title="PatientSim", theme=gr.themes.Soft(), css=CUSTOM_CSS) as de
|
|
| 1088 |
with gr.Column(scale=2):
|
| 1089 |
auto_chatbot = gr.Chatbot(
|
| 1090 |
label="Doctor–Patient Dialogue",
|
| 1091 |
-
height=
|
| 1092 |
show_label=True,
|
| 1093 |
avatar_images=(
|
| 1094 |
_DOCTOR_AVATAR,
|
|
@@ -1119,7 +1178,7 @@ with gr.Blocks(title="PatientSim", theme=gr.themes.Soft(), css=CUSTOM_CSS) as de
|
|
| 1119 |
with gr.Column(scale=2):
|
| 1120 |
chatbot = gr.Chatbot(
|
| 1121 |
label="Consultation",
|
| 1122 |
-
height=
|
| 1123 |
show_label=True,
|
| 1124 |
avatar_images=(
|
| 1125 |
_DOCTOR_AVATAR,
|
|
|
|
| 163 |
)
|
| 164 |
|
| 165 |
|
| 166 |
+
# ---------------------------------------------------------------------------
|
| 167 |
+
# Profile HTML helpers (card-based layout matching recap style)
|
| 168 |
+
# ---------------------------------------------------------------------------
|
| 169 |
+
def _profile_item(label: str, val) -> str:
|
| 170 |
+
"""Build a single key-value item inside a profile card."""
|
| 171 |
safe_val = html.escape(str(val)) if val not in (None, "") else "N/A"
|
| 172 |
safe_label = html.escape(str(label))
|
| 173 |
return (
|
| 174 |
+
f"<div style='display:flex;gap:6px;padding:5px 0;"
|
| 175 |
+
f"border-bottom:1px solid #f3f4f6;font-size:13px;line-height:1.5'>"
|
| 176 |
+
f"<span style='color:#6b7280;white-space:nowrap;min-width:110px;"
|
| 177 |
+
f"font-weight:500'>{safe_label}</span>"
|
| 178 |
+
f"<span style='color:#1f2937;flex:1'>{safe_val}</span>"
|
| 179 |
+
f"</div>"
|
| 180 |
)
|
| 181 |
|
| 182 |
|
| 183 |
+
def _profile_card(icon: str, title: str, items_html: str, accent: str = "#3b82f6") -> str:
|
| 184 |
+
"""Build a styled card section for the profile panel."""
|
| 185 |
return (
|
| 186 |
+
f"<div style='background:var(--background-fill-primary,#fff);"
|
| 187 |
+
f"border:1px solid var(--border-color-primary,#e5e7eb);border-radius:10px;"
|
| 188 |
+
f"padding:14px 16px;margin-bottom:10px'>"
|
| 189 |
+
f"<div style='display:flex;align-items:center;gap:8px;margin-bottom:10px;"
|
| 190 |
+
f"padding-bottom:8px;border-bottom:2px solid {accent}'>"
|
| 191 |
+
f"<span style='font-size:16px'>{icon}</span>"
|
| 192 |
+
f"<span style='font-size:13px;font-weight:600;letter-spacing:0.03em;"
|
| 193 |
+
f"text-transform:uppercase;color:{accent}'>{html.escape(title)}</span>"
|
| 194 |
+
f"</div>"
|
| 195 |
+
f"{items_html}"
|
| 196 |
+
f"</div>"
|
| 197 |
)
|
| 198 |
|
| 199 |
|
| 200 |
def build_profile_html(p: dict) -> str:
|
| 201 |
+
hadm_id = p.get("hadm_id", "")
|
| 202 |
+
avatar_url = _PATIENT_AVATAR_URLS.get(hadm_id, list(_PATIENT_AVATAR_URLS.values())[0])
|
| 203 |
+
safe_avatar = html.escape(avatar_url)
|
| 204 |
+
|
| 205 |
+
# Header with avatar
|
| 206 |
+
header = (
|
| 207 |
+
f"<div style='text-align:center;margin-bottom:14px'>"
|
| 208 |
+
f"<img src='{safe_avatar}' style='width:52px;height:52px;border-radius:50%;"
|
| 209 |
+
f"background:#f3f4f6;padding:3px;margin-bottom:6px' alt='Patient'>"
|
| 210 |
+
f"<div style='font-size:15px;font-weight:600;color:#1f2937'>Patient Profile</div>"
|
| 211 |
+
f"</div>"
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
basic = _profile_card("👤", "Demographics",
|
| 215 |
+
_profile_item("Age", p.get("age"))
|
| 216 |
+
+ _profile_item("Gender", p.get("gender"))
|
| 217 |
+
+ _profile_item("Race", p.get("race"))
|
| 218 |
+
+ _profile_item("Transport", p.get("arrival_transport")),
|
| 219 |
+
accent="#3b82f6",
|
| 220 |
)
|
| 221 |
+
social = _profile_card("🏠", "Social History",
|
| 222 |
+
_profile_item("Tobacco", p.get("tobacco"))
|
| 223 |
+
+ _profile_item("Alcohol", p.get("alcohol"))
|
| 224 |
+
+ _profile_item("Illicit Drug", p.get("illicit_drug"))
|
| 225 |
+
+ _profile_item("Exercise", p.get("exercise"))
|
| 226 |
+
+ _profile_item("Marital Status", p.get("marital_status"))
|
| 227 |
+
+ _profile_item("Children", p.get("children"))
|
| 228 |
+
+ _profile_item("Living Situation", p.get("living_situation"))
|
| 229 |
+
+ _profile_item("Occupation", p.get("occupation"))
|
| 230 |
+
+ _profile_item("Insurance", p.get("insurance")),
|
| 231 |
+
accent="#10b981",
|
| 232 |
)
|
| 233 |
+
history = _profile_card("📋", "Previous Medical History",
|
| 234 |
+
_profile_item("Allergies", p.get("allergies"))
|
| 235 |
+
+ _profile_item("Family History", p.get("family_medical_history"))
|
| 236 |
+
+ _profile_item("Medical Devices", p.get("medical_device"))
|
| 237 |
+
+ _profile_item("Prior History", p.get("medical_history")),
|
| 238 |
+
accent="#f59e0b",
|
| 239 |
)
|
| 240 |
+
visit = _profile_card("🩺", "Current Visit",
|
| 241 |
+
_profile_item("Present Illness (+)", p.get("present_illness_positive"))
|
| 242 |
+
+ _profile_item("Present Illness (−)", p.get("present_illness_negative"))
|
| 243 |
+
+ _profile_item("Chief Complaint", p.get("chiefcomplaint"))
|
| 244 |
+
+ _profile_item("Pain (0–10)", p.get("pain"))
|
| 245 |
+
+ _profile_item("Medications", p.get("medication"))
|
| 246 |
+
+ _profile_item("Disposition", p.get("disposition"))
|
| 247 |
+
+ _profile_item("Diagnosis", p.get("diagnosis")),
|
| 248 |
+
accent="#ef4444",
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
return (
|
| 252 |
+
f"<div style='font-family:Noto Sans KR,Noto Sans,Malgun Gothic,Apple SD Gothic Neo,sans-serif;"
|
| 253 |
+
f"font-size:14px;line-height:1.5;background:var(--color-accent-soft,#f0f7ff);"
|
| 254 |
+
f"border:1px solid var(--border-color-primary,#e5e7eb);border-radius:12px;"
|
| 255 |
+
f"padding:18px 16px;max-height:400px;overflow-y:auto'>"
|
| 256 |
+
f"{header}{basic}{social}{history}{visit}"
|
| 257 |
+
f"</div>"
|
| 258 |
)
|
|
|
|
| 259 |
|
| 260 |
|
| 261 |
def build_blind_profile_html(p: dict) -> str:
|
| 262 |
"""Show only basic demographic info for practice mode without full case details."""
|
| 263 |
+
hadm_id = p.get("hadm_id", "")
|
| 264 |
+
avatar_url = _PATIENT_AVATAR_URLS.get(hadm_id, list(_PATIENT_AVATAR_URLS.values())[0])
|
| 265 |
+
safe_avatar = html.escape(avatar_url)
|
| 266 |
+
|
| 267 |
+
header = (
|
| 268 |
+
f"<div style='text-align:center;margin-bottom:14px'>"
|
| 269 |
+
f"<img src='{safe_avatar}' style='width:52px;height:52px;border-radius:50%;"
|
| 270 |
+
f"background:#f3f4f6;padding:3px;margin-bottom:6px' alt='Patient'>"
|
| 271 |
+
f"<div style='font-size:15px;font-weight:600;color:#1f2937'>Patient Info</div>"
|
| 272 |
+
f"<div style='font-size:12px;color:#9ca3af;font-style:italic;margin-top:4px'>"
|
| 273 |
+
f"Basic demographics only — gather the rest through consultation.</div>"
|
| 274 |
+
f"</div>"
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
basic = _profile_card("👤", "Demographics",
|
| 278 |
+
_profile_item("Age", p.get("age"))
|
| 279 |
+
+ _profile_item("Gender", p.get("gender"))
|
| 280 |
+
+ _profile_item("Race", p.get("race"))
|
| 281 |
+
+ _profile_item("Transport", p.get("arrival_transport")),
|
| 282 |
+
accent="#3b82f6",
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
hint = (
|
| 286 |
+
"<div style='background:var(--background-fill-primary,#fff);"
|
| 287 |
+
"border:1px dashed #d1d5db;border-radius:10px;padding:16px;text-align:center;"
|
| 288 |
+
"color:#9ca3af;font-size:13px'>"
|
| 289 |
+
"🔍 Additional information is hidden.<br>"
|
| 290 |
+
"Interview the patient to uncover their history."
|
| 291 |
+
"</div>"
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
return (
|
| 295 |
+
f"<div style='font-family:Noto Sans KR,Noto Sans,Malgun Gothic,Apple SD Gothic Neo,sans-serif;"
|
| 296 |
+
f"font-size:14px;line-height:1.5;background:var(--color-accent-soft,#f0f7ff);"
|
| 297 |
+
f"border:1px solid var(--border-color-primary,#e5e7eb);border-radius:12px;"
|
| 298 |
+
f"padding:18px 16px'>"
|
| 299 |
+
f"{header}{basic}{hint}"
|
| 300 |
+
f"</div>"
|
| 301 |
)
|
|
|
|
| 302 |
|
| 303 |
|
| 304 |
# ---------------------------------------------------------------------------
|
|
|
|
| 1147 |
with gr.Column(scale=2):
|
| 1148 |
auto_chatbot = gr.Chatbot(
|
| 1149 |
label="Doctor–Patient Dialogue",
|
| 1150 |
+
height=700,
|
| 1151 |
show_label=True,
|
| 1152 |
avatar_images=(
|
| 1153 |
_DOCTOR_AVATAR,
|
|
|
|
| 1178 |
with gr.Column(scale=2):
|
| 1179 |
chatbot = gr.Chatbot(
|
| 1180 |
label="Consultation",
|
| 1181 |
+
height=700,
|
| 1182 |
show_label=True,
|
| 1183 |
avatar_images=(
|
| 1184 |
_DOCTOR_AVATAR,
|