Files
rpa_vision_v3/docs/UX_PAUSE_BUBBLES_FIX_2026-05-08.md
2026-05-09 11:32:52 +02:00

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 :

  1. Trois affichages parallèles — toast Tkinter paused_toast en haut à droite, bulle dans la fenêtre chat Tkinter, popup PauseDialog dans le frontend VWB React.
  2. Message tronqué dans la bulle chat — pas de scroll interne, donc un pause_message long (>200 chars, fréquent côté serveur) déborde de la fenêtre.
  3. Bouton "Annuler" inopérant — clic sans effet visuel.
  4. 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_event ligne 768 ignore explicitement lea:abort_acked (silencieux côté UI).
  • Aucun lea:resumed n'est émis pour un abort (ce serait sémantiquement faux).
  • Donc _close_active_paused_bubble n'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 :

  1. UN SEUL popup (la bulle chat dans la fenêtre Léa, pas de toast Tkinter en plus).
  2. Message long visible avec scroll interne si débordement.
  3. Boutons Continuer / Annuler fonctionnels.
  4. 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+RC:\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_bubble aussi.
  • agent_v0/agent_v1/ui/notifications.py : notify_message — suppression du déclenchement show_paused_toast BLOCAGE.
  • agent_v0/agent_v1/core/executor.py : Plan B polling — suppression du show_paused_toast direct, remplacé par fallback uniquement si _chat_window_ref est 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).