chore: ajouter agent_v0/ au tracking git (était un repo embarqué)
Suppression du .git embarqué dans agent_v0/ — le code est maintenant tracké normalement dans le repo principal. Inclut : agent_v1 (client), server_v1 (streaming), lea_ui (chat client) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
190
agent_v0/agent_v1/ui/shared_state.py
Normal file
190
agent_v0/agent_v1/ui/shared_state.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# agent_v1/ui/shared_state.py
|
||||
"""
|
||||
Etat partage entre le systray et le chat Lea. Thread-safe.
|
||||
|
||||
Point central de verite pour l'etat de l'agent :
|
||||
- Enregistrement en cours (oui/non, nom de la tache)
|
||||
- Replay en cours
|
||||
- Compteur d'actions
|
||||
|
||||
Les deux composants UI (SmartTrayV1 et ChatWindow) lisent et ecrivent
|
||||
dans cet objet. Chaque changement notifie tous les listeners enregistres.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from typing import Any, Callable, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AgentState:
|
||||
"""Etat partage entre le systray et le chat Lea. Thread-safe."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._lock = threading.Lock()
|
||||
|
||||
# Etat d'enregistrement
|
||||
self._recording = False
|
||||
self._recording_name = ""
|
||||
self._actions_count = 0
|
||||
|
||||
# Etat de replay
|
||||
self._replay_active = False
|
||||
|
||||
# Callbacks de demarrage/arret de session (relies au moteur agent)
|
||||
self._on_start: Optional[Callable[[str], None]] = None
|
||||
self._on_stop: Optional[Callable[[], None]] = None
|
||||
|
||||
# Listeners notifies a chaque changement d'etat
|
||||
self._listeners: List[Callable[["AgentState"], None]] = []
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Proprietes en lecture seule (thread-safe)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def is_recording(self) -> bool:
|
||||
with self._lock:
|
||||
return self._recording
|
||||
|
||||
@property
|
||||
def recording_name(self) -> str:
|
||||
with self._lock:
|
||||
return self._recording_name
|
||||
|
||||
@property
|
||||
def actions_count(self) -> int:
|
||||
with self._lock:
|
||||
return self._actions_count
|
||||
|
||||
@property
|
||||
def is_replay_active(self) -> bool:
|
||||
with self._lock:
|
||||
return self._replay_active
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Mutations (thread-safe, notifient les listeners)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def start_recording(self, name: str) -> None:
|
||||
"""Demarre un enregistrement (appele depuis systray OU chat).
|
||||
|
||||
Appelle le callback on_start si defini, puis notifie les listeners.
|
||||
"""
|
||||
with self._lock:
|
||||
if self._recording:
|
||||
logger.warning("Enregistrement deja en cours, ignore")
|
||||
return
|
||||
self._recording = True
|
||||
self._recording_name = name
|
||||
self._actions_count = 0
|
||||
on_start = self._on_start
|
||||
|
||||
logger.info("Enregistrement demarre : %s", name)
|
||||
|
||||
# Appeler le callback moteur (hors du lock pour eviter deadlock)
|
||||
if on_start is not None:
|
||||
try:
|
||||
on_start(name)
|
||||
except Exception as e:
|
||||
logger.error("Erreur demarrage session : %s", e)
|
||||
# Annuler l'enregistrement si le moteur echoue
|
||||
with self._lock:
|
||||
self._recording = False
|
||||
self._recording_name = ""
|
||||
self._notify_listeners()
|
||||
raise
|
||||
|
||||
self._notify_listeners()
|
||||
|
||||
def stop_recording(self) -> None:
|
||||
"""Arrete l'enregistrement (appele depuis systray OU chat).
|
||||
|
||||
Appelle le callback on_stop si defini, puis notifie les listeners.
|
||||
"""
|
||||
with self._lock:
|
||||
if not self._recording:
|
||||
logger.debug("Pas d'enregistrement en cours, ignore")
|
||||
return
|
||||
self._recording = False
|
||||
name = self._recording_name
|
||||
count = self._actions_count
|
||||
on_stop = self._on_stop
|
||||
|
||||
logger.info("Enregistrement arrete : %s (%d actions)", name, count)
|
||||
|
||||
# Appeler le callback moteur
|
||||
if on_stop is not None:
|
||||
try:
|
||||
on_stop()
|
||||
except Exception as e:
|
||||
logger.error("Erreur arret session : %s", e)
|
||||
|
||||
self._notify_listeners()
|
||||
|
||||
def update_actions_count(self, count: int) -> None:
|
||||
"""Met a jour le compteur d'actions (appele par le moteur agent)."""
|
||||
with self._lock:
|
||||
self._actions_count = count
|
||||
self._notify_listeners()
|
||||
|
||||
def set_replay_active(self, active: bool) -> None:
|
||||
"""Active ou desactive le mode replay."""
|
||||
with self._lock:
|
||||
if self._replay_active == active:
|
||||
return
|
||||
self._replay_active = active
|
||||
logger.info("Replay %s", "actif" if active else "termine")
|
||||
self._notify_listeners()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Enregistrement des callbacks et listeners
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def set_on_start(self, callback: Callable[[str], None]) -> None:
|
||||
"""Definit le callback appele quand un enregistrement demarre.
|
||||
|
||||
Ce callback est le pont vers le moteur agent (AgentV1.start_session).
|
||||
"""
|
||||
with self._lock:
|
||||
self._on_start = callback
|
||||
|
||||
def set_on_stop(self, callback: Callable[[], None]) -> None:
|
||||
"""Definit le callback appele quand un enregistrement s'arrete.
|
||||
|
||||
Ce callback est le pont vers le moteur agent (AgentV1.stop_session).
|
||||
"""
|
||||
with self._lock:
|
||||
self._on_stop = callback
|
||||
|
||||
def on_change(self, callback: Callable[["AgentState"], None]) -> None:
|
||||
"""Enregistre un listener notifie a chaque changement d'etat.
|
||||
|
||||
Les listeners sont appeles dans un thread separe pour ne pas
|
||||
bloquer l'appelant.
|
||||
"""
|
||||
with self._lock:
|
||||
self._listeners.append(callback)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Notification interne
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _notify_listeners(self) -> None:
|
||||
"""Notifie tous les listeners enregistres du changement d'etat."""
|
||||
with self._lock:
|
||||
listeners = list(self._listeners)
|
||||
|
||||
for listener in listeners:
|
||||
try:
|
||||
# Appel dans un thread pour ne pas bloquer
|
||||
threading.Thread(
|
||||
target=listener,
|
||||
args=(self,),
|
||||
daemon=True,
|
||||
).start()
|
||||
except Exception as e:
|
||||
logger.error("Erreur notification listener : %s", e)
|
||||
Reference in New Issue
Block a user