9.3 KiB
UX Fix — bulle pause Léa (1 affichage, scroll, boutons fonctionnels)
Date : 2026-05-08 (matin, J démo GHT Sud 95)
Branche : feature/qw-suite-mai
Périmètre : agent_v0/agent_v1/ uniquement (client Windows). Pas de modif serveur, pas de modif VWB.
1. Constat utilisateur
Verbatim Dom (chat avant démo) :
"Pour le thinker, il y en a 3 maintenant ! un en haut à droite de l'écran, l'autre dans le chat (qui n'a pas de scroll donc message tronqué) puis dans le vwb. Je précise également que les boutons annulé sur toutes les questions ne fonctionnent pas !"
Quatre bugs UX simultanés au déclenchement d'un pause_for_human :
- Trois affichages parallèles — toast Tkinter
paused_toasten haut à droite, bulle dans la fenêtre chat Tkinter, popup PauseDialog dans le frontend VWB React. - Message tronqué dans la bulle chat — pas de scroll interne, donc un
pause_messagelong (>200 chars, fréquent côté serveur) déborde de la fenêtre. - Bouton "Annuler" inopérant — clic sans effet visuel.
- Bouton "Continuer" sans feedback — pas de plainte explicite mais aucune confirmation visuelle après clic.
2. Diagnostic technique
Bug 1 — 3 toasts paused_toast en parallèle
grep -rn "show_paused_toast" agent_v0/agent_v1/ a remonté trois call sites qui se déclenchent en cascade pour la même pause :
| # | Fichier | Ligne | Contexte |
|---|---|---|---|
| (a) | core/executor.py |
1831 | Plan B polling — replay_paused=True détecté |
| (b) | ui/chat_window.py |
860 | _add_paused_bubble appelait aussi le toast en complément |
| (c) | ui/notifications.py |
156 | notify_message(BLOCAGE) déclenché en parallèle par executor (mécanisme legacy) |
Chacun des trois chemins force le toast topmost ⇒ Dom voit 3 popups + 1 bulle = 4 éléments UI. Confirmé dans agent_debug.log Windows :
2026-05-08 10:24:37,750 [paused_toast] INFO: paused_toast scheduled on existing Tk root
2026-05-08 10:26:45,586 [paused_toast] INFO: paused_toast scheduled on existing Tk root
2026-05-08 10:28:46,217 [paused_toast] INFO: paused_toast scheduled on existing Tk root
Plus la PauseDialog du VWB côté Linux (non concernée par la démo Windows mais visible si on ouvre le navigateur).
Bug 2 — bulle non scrollable
La fenêtre chat dispose déjà d'un Canvas + Scrollbar global (_build_messages_area, ligne 464-507). Mais _render_paused_bubble n'appelait pas _scroll_to_bottom() après le pack(), donc une bulle insérée en bas de la zone restait potentiellement masquée. Et le contenu de la bulle (reason) était rendu via un tk.Label à wraplength fixe : pas de scroll interne pour les messages très longs (>300 chars).
Bug 3 — bouton "Annuler" inopérant
Lecture de _on_paused_abort (ligne 975) :
self._bus.abort_replay(replay_id) # émet lea:replay_abort
if self._active_paused_bubble:
self._active_paused_bubble["btn_resume"].config(state="disabled")
self._active_paused_bubble["btn_abort"].config(state="disabled")
Le bus émet bien lea:replay_abort. Côté serveur (agent_chat/app.py:1720), le handler met execution_status["running"] = False et émet lea:abort_acked. Mais :
_on_lea_eventligne 768 ignore explicitementlea:abort_acked(silencieux côté UI).- Aucun
lea:resumedn'est émis pour un abort (ce serait sémantiquement faux). - Donc
_close_active_paused_bubblen'est jamais déclenché ⇒ la bulle reste affichée avec ses boutons disabled, sans aucun message de fermeture. Pour Dom, "rien ne se passe".
Bug 4 — bouton "Continuer" sans feedback immédiat
Même mécanisme : le clic émet lea:replay_resume, le serveur relaie en POST HTTP vers le streaming server. La fermeture de la bulle ne survient qu'à la réception ultérieure de lea:resumed (plusieurs secondes plus tard). Pas de feedback sur le clic lui-même.
3. Solution implémentée
3.1 Un seul affichage canonique : la bulle chat
core/executor.py — Plan B simplifié :
# UX fix 8 mai 2026 : un seul affichage. La bulle ChatWindow EST l'affichage
# canonique (force show + topmost + bell sonore). Plus de paused_toast en double.
chat_window = getattr(self, "_chat_window_ref", None)
if chat_window is not None:
chat_window._add_paused_bubble(payload)
else:
# Fallback headless / tests : toast Tkinter custom
from ..ui.paused_toast import show_paused_toast
show_paused_toast(title=..., message=pause_msg[:300])
ui/chat_window.py:_add_paused_bubble — suppression du show_paused_toast en complément, remplacé par un root.bell() natif Tkinter + attributes("-topmost", True) + lift() pour la mise au premier plan.
ui/notifications.py:notify_message — suppression du show_paused_toast BLOCAGE (devenu redondant). Plyer reste actif comme notification système Windows discrète.
3.2 Scroll dans la bulle pour messages longs
_render_paused_bubble — remplacement du tk.Label par un tk.Text read-only avec hauteur calculée dynamiquement (2-8 lignes selon longueur), et scrollbar interne au-delà de 280 caractères :
approx_lines = max(2, min(8, (len(reason_str) // 60) + 1))
reason_text = tk.Text(msg_frame, height=approx_lines, wrap=tk.WORD, ...)
reason_text.insert("1.0", reason_str)
reason_text.configure(state="disabled")
if len(reason_str) > 280:
reason_scroll = tk.Scrollbar(msg_frame, command=reason_text.yview, ...)
reason_text.configure(yscrollcommand=reason_scroll.set)
Ajout d'un appel self._scroll_to_bottom() à la fin de _render_paused_bubble ET de _render_action_bubble pour que la bulle apparaisse toujours dans la zone visible.
3.3 Fermeture immédiate sur Annuler + feedback visuel
_on_paused_abort :
emitted = self._bus.abort_replay(replay_id) if (self._bus and self._bus.connected) else False
self._disable_paused_buttons()
self._update_paused_feedback("✗ Annulé" if emitted else "✗ Annulé (bus indisponible)")
self._close_active_paused_bubble(reason="abort_local") # NEW : fermeture locale immédiate
_on_paused_resume : même structure avec feedback "→ Reprise demandée…". La bulle reste visible avec boutons disabled jusqu'à réception de lea:resumed qui déclenche _close_active_paused_bubble("lea:resumed").
Helpers ajoutés : _disable_paused_buttons() et _update_paused_feedback(text). Un feedback_label (label vide) est intégré dans la bulle au render et mis à jour à chaque clic.
4. Test isolé
Script ajouté : agent_v0/agent_v1/tools/test_lea_pause_flow.py (déployé C:\rpa_vision\agent_v1\tools\).
Commande exacte sur le PC Windows :
cd C:\rpa_vision
.venv\Scripts\python.exe -m agent_v1.tools.test_lea_pause_flow
Le script ouvre une ChatWindow, simule un paused_need_help avec un message de 350 chars (« Je n'arrive pas à trouver le champ Numéro de dossier patient... »), et garde la fenêtre ouverte 30s pour validation visuelle. Vérifications attendues :
- UN SEUL popup (la bulle chat dans la fenêtre Léa, pas de toast Tkinter en plus).
- Message long visible avec scroll interne si débordement.
- Boutons Continuer / Annuler fonctionnels.
- Clic Annuler ⇒ bulle fermée + feedback
✗ Annulé.
5. Tests automatisés exécutés
$ pytest tests/unit/test_lea_notifications.py
101 passed in 0.69s
$ pytest tests/integration/test_chat_window_templates.py tests/integration/test_feedback_bus_client.py
35 passed
Aucune régression. Les tests existants vérifient notify_message(BLOCAGE) retourne True — le retour reste True via notify(...) (le toast en complément a juste été retiré).
6. Déploiement Windows
| Fichier | MD5 Linux | MD5 Windows | Match |
|---|---|---|---|
agent_v1/ui/chat_window.py |
50597f1f7531ab8e15fdc91e3a03e98a |
identique | OK |
agent_v1/ui/notifications.py |
8382ce3cbbc819af0e1a25fc708a0596 |
identique | OK |
agent_v1/core/executor.py |
dfec3a9da28ef44019fd705404d670a5 |
identique | OK |
agent_v1/tools/test_lea_pause_flow.py |
edd66b613430d10e1fce8c50f478c90c |
identique | OK |
Cache __pycache__ purgé sur Windows :
Get-ChildItem -Recurse -Path C:\rpa_vision\agent_v1 -Include *.pyc | Remove-Item -Force
Get-ChildItem -Recurse -Path C:\rpa_vision\agent_v1 -Include __pycache__ -Directory | Remove-Item -Recurse -Force
Action restante avant démo : redémarrer l'agent Léa V1 sur le PC Windows (le client doit recharger les modules). Procédure standard : tray icon Léa → Quitter, puis Win+R → C:\rpa_vision\start_lea.cmd (ou équivalent dans la doc reference_windows_pc.md).
7. Synthèse fichiers modifiés
agent_v0/agent_v1/ui/chat_window.py:_add_paused_bubble,_render_paused_bubble,_on_paused_resume,_on_paused_abort+ helpers_disable_paused_buttons,_update_paused_feedback. Auto-scroll ajouté à_render_action_bubbleaussi.agent_v0/agent_v1/ui/notifications.py:notify_message— suppression du déclenchementshow_paused_toastBLOCAGE.agent_v0/agent_v1/core/executor.py: Plan B polling — suppression dushow_paused_toastdirect, remplacé par fallback uniquement si_chat_window_refest None.agent_v0/agent_v1/tools/test_lea_pause_flow.py: nouveau script de smoke test.
Aucun fichier serveur ni VWB modifié. Conforme feedback_agent_frozen.md (modif client validée par Dom ce matin).