RolandM Claude Opus 4.7 (1M context) commited on
Commit
eaecb1a
·
1 Parent(s): d29face

Surface errors as toasts instead of fake assistant messages

Browse files

Failed 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>

Files changed (1) hide show
  1. app.py +46 -15
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
- # Runs after dropdown changes and after each new turn is added.
 
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": assistant_text}
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
- assistant_text = (
301
- "Something went wrong on Prisma's end — please try again. "
302
- f"(Details: {exc})"
 
 
 
 
303
  )
304
-
305
- chat_display = chat_display + [
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, "", dropdown_update
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
  # ---------------------------------------------------------------------------