Spaces:
Running
Running
Surface errors as toasts instead of fake assistant messages
Browse filesFailed turns no longer pollute the chat history with apology bubbles. The
user's text stays in the input on failure so they can edit and retry, and
chat_display now stays 1:1 with state["evaluations"]. Adds dark-themed
styling for Gradio toasts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
app.py
CHANGED
|
@@ -204,14 +204,32 @@ ul.options li.selected {
|
|
| 204 |
box-shadow: 0 0 14px rgba(252, 211, 77, 0.45);
|
| 205 |
pointer-events: none;
|
| 206 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
"""
|
| 208 |
|
| 209 |
|
| 210 |
# JS that highlights the user message at the currently-selected turn index.
|
| 211 |
-
#
|
|
|
|
| 212 |
HIGHLIGHT_TURN_JS = """
|
| 213 |
(turn_index) => {
|
| 214 |
-
// Remove any existing highlight.
|
| 215 |
document.querySelectorAll('.selected-turn').forEach(el => {
|
| 216 |
el.classList.remove('selected-turn');
|
| 217 |
});
|
|
@@ -220,8 +238,6 @@ HIGHLIGHT_TURN_JS = """
|
|
| 220 |
return turn_index;
|
| 221 |
}
|
| 222 |
|
| 223 |
-
// Find user message bubbles. Try several selectors since Gradio's
|
| 224 |
-
// chatbot DOM classes can vary; use the first that matches.
|
| 225 |
const candidates = [
|
| 226 |
'.message-row.user-row',
|
| 227 |
'[data-testid="user"]',
|
|
@@ -266,6 +282,15 @@ def chat_step(
|
|
| 266 |
):
|
| 267 |
"""Process one user turn: call the model, update state and UI.
|
| 268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
Returns updates for (chatbot, state, msg_in, turn_dropdown).
|
| 270 |
"""
|
| 271 |
user_message = (user_message or "").strip()
|
|
@@ -288,24 +313,30 @@ def chat_step(
|
|
| 288 |
|
| 289 |
try:
|
| 290 |
parsed = CLIENT.generate(state["history"])
|
| 291 |
-
assistant_text = parsed.response
|
| 292 |
state["history"].append(
|
| 293 |
-
{"role": "assistant", "content":
|
| 294 |
)
|
| 295 |
state["evaluations"].append(parsed.evaluation)
|
| 296 |
state["turn_count"] += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
except (InferenceError, EvaluationParseError) as exc:
|
| 298 |
# Roll back the unanswered user message so retries send clean history.
|
| 299 |
state["history"].pop()
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
{"role": "user", "content": user_message},
|
| 307 |
-
{"role": "assistant", "content": assistant_text},
|
| 308 |
-
]
|
| 309 |
|
| 310 |
n_evals = len(state["evaluations"])
|
| 311 |
if n_evals > 0:
|
|
@@ -314,7 +345,7 @@ def chat_step(
|
|
| 314 |
else:
|
| 315 |
dropdown_update = gr.Dropdown(choices=[], value=None)
|
| 316 |
|
| 317 |
-
return chat_display, state,
|
| 318 |
|
| 319 |
|
| 320 |
# ---------------------------------------------------------------------------
|
|
|
|
| 204 |
box-shadow: 0 0 14px rgba(252, 211, 77, 0.45);
|
| 205 |
pointer-events: none;
|
| 206 |
}
|
| 207 |
+
|
| 208 |
+
/* Warning/info/error toast notifications */
|
| 209 |
+
.toast,
|
| 210 |
+
.toast-body,
|
| 211 |
+
.toast-text,
|
| 212 |
+
.gr-toast,
|
| 213 |
+
[class~="toast"] {
|
| 214 |
+
background-color: #2a2a44 !important;
|
| 215 |
+
color: #e5e7eb !important;
|
| 216 |
+
border: 1px solid #ef4444 !important;
|
| 217 |
+
}
|
| 218 |
+
.toast .icon,
|
| 219 |
+
.toast svg,
|
| 220 |
+
.gr-toast svg,
|
| 221 |
+
[class~="toast"] svg {
|
| 222 |
+
color: #ef4444 !important;
|
| 223 |
+
fill: #ef4444 !important;
|
| 224 |
+
}
|
| 225 |
"""
|
| 226 |
|
| 227 |
|
| 228 |
# JS that highlights the user message at the currently-selected turn index.
|
| 229 |
+
# Since errored attempts are no longer added to the chat, the dropdown's
|
| 230 |
+
# turn index maps directly to the Nth user message in the DOM.
|
| 231 |
HIGHLIGHT_TURN_JS = """
|
| 232 |
(turn_index) => {
|
|
|
|
| 233 |
document.querySelectorAll('.selected-turn').forEach(el => {
|
| 234 |
el.classList.remove('selected-turn');
|
| 235 |
});
|
|
|
|
| 238 |
return turn_index;
|
| 239 |
}
|
| 240 |
|
|
|
|
|
|
|
| 241 |
const candidates = [
|
| 242 |
'.message-row.user-row',
|
| 243 |
'[data-testid="user"]',
|
|
|
|
| 282 |
):
|
| 283 |
"""Process one user turn: call the model, update state and UI.
|
| 284 |
|
| 285 |
+
On success, the user message and assistant response are added to
|
| 286 |
+
``chat_display`` and a new evaluation is recorded. On failure, the
|
| 287 |
+
chat is NOT modified — the error is surfaced via gr.Warning, and the
|
| 288 |
+
user's text is kept in the input box so they can edit and retry.
|
| 289 |
+
|
| 290 |
+
This keeps chat_display in perfect 1:1 correspondence with
|
| 291 |
+
``state["evaluations"]``, so the dropdown's turn index always maps
|
| 292 |
+
cleanly to the Nth user message in the DOM.
|
| 293 |
+
|
| 294 |
Returns updates for (chatbot, state, msg_in, turn_dropdown).
|
| 295 |
"""
|
| 296 |
user_message = (user_message or "").strip()
|
|
|
|
| 313 |
|
| 314 |
try:
|
| 315 |
parsed = CLIENT.generate(state["history"])
|
|
|
|
| 316 |
state["history"].append(
|
| 317 |
+
{"role": "assistant", "content": parsed.response}
|
| 318 |
)
|
| 319 |
state["evaluations"].append(parsed.evaluation)
|
| 320 |
state["turn_count"] += 1
|
| 321 |
+
|
| 322 |
+
chat_display = chat_display + [
|
| 323 |
+
{"role": "user", "content": user_message},
|
| 324 |
+
{"role": "assistant", "content": parsed.response},
|
| 325 |
+
]
|
| 326 |
+
msg_in_value = "" # clear input on success
|
| 327 |
except (InferenceError, EvaluationParseError) as exc:
|
| 328 |
# Roll back the unanswered user message so retries send clean history.
|
| 329 |
state["history"].pop()
|
| 330 |
+
# Log technical details to the container log for debugging.
|
| 331 |
+
print(f"[error] {type(exc).__name__}: {exc}")
|
| 332 |
+
# Surface a friendly notification to the user without polluting the
|
| 333 |
+
# chat history. The error attempt does not appear as a bubble.
|
| 334 |
+
gr.Warning(
|
| 335 |
+
"I wasn't able to respond properly to that. "
|
| 336 |
+
"Try rephrasing or asking something else."
|
| 337 |
)
|
| 338 |
+
# Keep the user's text in the input box so they can edit and retry.
|
| 339 |
+
msg_in_value = user_message
|
|
|
|
|
|
|
|
|
|
| 340 |
|
| 341 |
n_evals = len(state["evaluations"])
|
| 342 |
if n_evals > 0:
|
|
|
|
| 345 |
else:
|
| 346 |
dropdown_update = gr.Dropdown(choices=[], value=None)
|
| 347 |
|
| 348 |
+
return chat_display, state, msg_in_value, dropdown_update
|
| 349 |
|
| 350 |
|
| 351 |
# ---------------------------------------------------------------------------
|