feat(lea): bulle paused_need_help interactive — asset démo majeur

Quand Léa bascule en pause supervisée (event 'lea:paused'), affichage d'une
bulle dédiée dans ChatWindow avec encadré orangé, raison de la pause, et deux
boutons Continuer/Annuler. C'est le moment qui incarne la différence RPA classique
vs Léa devant Carvella : Léa SAIT qu'elle ne sait pas et demande de l'aide.

Architecture (canal SocketIO bidirectionnel, pas de nouvel endpoint streaming) :

  ChatWindow ──[lea:replay_resume]──> agent_chat ──POST /resume──> streaming
  ChatWindow ──[lea:replay_abort ]──> agent_chat (running=False local)

Composants ajoutés :
- agent_chat/app.py : handlers 'lea:replay_resume' / 'lea:replay_abort' +
  acks 'lea:resume_acked' / 'lea:abort_acked' pour feedback côté client
- network/feedback_bus.py : méthodes resume_replay() / abort_replay() avec
  helper _safe_emit (silencieux + retourne bool succès)
- ui/chat_window.py : palette PAUSED_*, _add_paused_bubble(),
  _render_paused_bubble(), _close_active_paused_bubble() (auto-fermeture
  sur lea:resumed/done), _on_paused_resume/abort

8 nouveaux tests pytest (4 handlers serveur + 4 méthodes client).
Total branche : 29/29 verts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-04-28 10:08:32 +02:00
parent 6154423a91
commit 2af3bc3b93
5 changed files with 314 additions and 0 deletions

View File

@@ -122,3 +122,28 @@ class FeedbackBusClient:
@property
def connected(self) -> bool:
return bool(self._sio.connected)
# ------------------------------------------------------------------
# Actions utilisateur depuis la bulle paused_need_help (J3.5)
# ------------------------------------------------------------------
def resume_replay(self, replay_id: str) -> bool:
"""Bouton Continuer : émet 'lea:replay_resume' vers agent_chat.
Retourne True si l'event a pu être émis, False sinon (déconnecté/erreur).
"""
return self._safe_emit("lea:replay_resume", {"replay_id": replay_id})
def abort_replay(self, replay_id: str) -> bool:
"""Bouton Annuler : émet 'lea:replay_abort' vers agent_chat."""
return self._safe_emit("lea:replay_abort", {"replay_id": replay_id})
def _safe_emit(self, event: str, payload: dict) -> bool:
try:
if not self._sio.connected:
return False
self._sio.emit(event, payload)
return True
except Exception:
logger.debug("FeedbackBus _safe_emit silenced", exc_info=True)
return False