feat: blocs conditionnels — skip automatique des dialogues absents
Le session_cleaner détecte les dialogues système (Enregistrer sous, Ouvrir, Confirmer, etc.) et marque les actions correspondantes comme conditionnelles. Au replay, si le dialogue n'apparaît pas (ex: Ctrl+S sauve silencieusement car le fichier existe), les actions du dialogue sont skippées automatiquement. Détection basée sur des patterns de noms de dialogues Windows FR/EN. Testé : seul le clic dans "Enregistrer sous" est conditionnel, les actions Bloc-notes/Rechercher/systray restent normales. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1131,6 +1131,98 @@ def _simple_build_replay(events: List[Dict[str, Any]], session_dir: Path) -> Lis
|
||||
}
|
||||
actions.append(action)
|
||||
|
||||
# ── Étape finale : détecter les blocs conditionnels (dialogues) ──
|
||||
# Quand le window_title change entre deux actions, les actions dans
|
||||
# la nouvelle fenêtre sont conditionnelles : elles ne s'exécutent que
|
||||
# si le dialogue apparaît effectivement au replay.
|
||||
# Ex: Ctrl+S → "Enregistrer sous" (conditionnel) → retour app
|
||||
actions = _mark_conditional_blocks(actions, events)
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
def _mark_conditional_blocks(
|
||||
actions: List[Dict[str, Any]], events: List[Dict[str, Any]],
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Marquer les actions qui appartiennent a un dialogue conditionnel.
|
||||
|
||||
Detecte les dialogues systeme transitoires (Enregistrer sous, Ouvrir,
|
||||
Confirmer, etc.) qui n'apparaissent que dans certains contextes.
|
||||
Au replay, si le dialogue n'est pas present → skip tout le bloc.
|
||||
|
||||
Methode : un dialogue systeme est une fenetre qui :
|
||||
1. N'a PAS de separateur " – " ou " - " (pas une app)
|
||||
2. N'apparait que pour 1-3 actions consecutives
|
||||
3. Est encadree par des actions dans une vraie app
|
||||
"""
|
||||
# Extraire le window_title de chaque evenement actionnable
|
||||
event_windows: List[str] = []
|
||||
for ev in events:
|
||||
inner = ev.get("event", {})
|
||||
etype = inner.get("type", "")
|
||||
if etype not in _ACTIONABLE_TYPES:
|
||||
continue
|
||||
win = inner.get("window", {}).get("title", "")
|
||||
event_windows.append(win)
|
||||
|
||||
def _is_app_window(title):
|
||||
"""True si le titre ressemble a une fenetre d'application (pas un dialogue)."""
|
||||
if not title or title == "unknown_window":
|
||||
return False
|
||||
# Les apps ont un separateur : "fichier.txt – Bloc-notes"
|
||||
return any(sep in title for sep in [" – ", " - ", " — "])
|
||||
|
||||
def _is_known_dialog(title):
|
||||
"""True si le titre est un dialogue systeme connu."""
|
||||
if not title:
|
||||
return False
|
||||
title_lower = title.lower().strip()
|
||||
dialog_patterns = (
|
||||
"enregistrer sous", "save as",
|
||||
"ouvrir", "open",
|
||||
"imprimer", "print",
|
||||
"confirmer", "confirmation", "confirm",
|
||||
"voulez-vous", "do you want",
|
||||
"avertissement", "warning",
|
||||
"erreur", "error",
|
||||
"propriétés", "properties",
|
||||
)
|
||||
return any(p in title_lower for p in dialog_patterns)
|
||||
|
||||
# Parcourir les actions et marquer les dialogues
|
||||
action_idx = 0
|
||||
n_setup = sum(1 for a in actions if a.get("_setup_action"))
|
||||
|
||||
for i, action in enumerate(actions):
|
||||
if action.get("_setup_action"):
|
||||
continue
|
||||
|
||||
if action_idx >= len(event_windows):
|
||||
break
|
||||
|
||||
win = event_windows[action_idx]
|
||||
action_idx += 1
|
||||
|
||||
if not win or win == "unknown_window":
|
||||
continue
|
||||
|
||||
# Marquer si c'est un dialogue connu OU une fenetre sans separateur app
|
||||
# entouree de fenetres d'app (transitoire)
|
||||
if _is_known_dialog(win):
|
||||
action["conditional_on_window"] = win
|
||||
logger.debug(
|
||||
"Action %s conditionnelle (dialogue connu) : '%s'",
|
||||
action.get("action_id", "?"), win,
|
||||
)
|
||||
|
||||
# Log resume
|
||||
n_conditional = sum(1 for a in actions if a.get("conditional_on_window"))
|
||||
if n_conditional:
|
||||
logger.info(
|
||||
"Blocs conditionnels : %d actions sur %d marquees comme dialogues",
|
||||
n_conditional, len(actions) - n_setup,
|
||||
)
|
||||
|
||||
return actions
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user