fix: 3 corrections — crop 80px, email AZERTY (@), icônes anchor match
1. Crop réduit de 150x150 à 80x80 (config + fallback serveur) Plus discriminant pour les icônes de barre de titre 2. Email AZERTY : supprimer raw_keys quand le texte contient des chars fusionnés depuis key_combos (@ de AltGr) → copier-coller Le @ était perdu car absent des raw_keys individuels 3. Anchor match : template matching sur screenshot entier puis élément SomEngine le plus proche (max 100px) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,8 +52,9 @@ API_TOKEN = os.environ.get("RPA_API_TOKEN", "")
|
||||
MAX_SESSION_DURATION_S = 60 * 60 # 1 heure
|
||||
SESSIONS_ROOT = BASE_DIR / "sessions"
|
||||
|
||||
# Paramètres Vision (Crops pour qwen3-vl)
|
||||
TARGETED_CROP_SIZE = (150, 150)
|
||||
# Paramètres Vision (Crops pour la résolution visuelle)
|
||||
# 80x80 : assez petit pour être discriminant (icônes), assez grand pour le contexte
|
||||
TARGETED_CROP_SIZE = (80, 80)
|
||||
SCREENSHOT_QUALITY = 85
|
||||
|
||||
# Floutage des données sensibles (conformité AI Act)
|
||||
|
||||
@@ -750,8 +750,8 @@ def _load_crop_for_event(
|
||||
pos = event_data.get("pos", [])
|
||||
if pos and len(pos) == 2:
|
||||
cx, cy = int(pos[0]), int(pos[1])
|
||||
# Crop 150x150 centré sur le clic (plus discriminant, moins de bruit)
|
||||
crop_size = 75
|
||||
# Crop 80x80 centré sur le clic (discriminant pour icônes)
|
||||
crop_size = 40
|
||||
x1 = max(0, cx - crop_size)
|
||||
y1 = max(0, cy - crop_size)
|
||||
x2 = min(img.width, cx + crop_size)
|
||||
@@ -953,6 +953,15 @@ def build_replay_from_raw_events(
|
||||
if evt_type in ("key_combo", "key_press"):
|
||||
keys = _sanitize_keys(evt.get("keys", []))
|
||||
printable = _key_combo_printable_char(keys)
|
||||
if not printable:
|
||||
# AltGr seul (AZERTY) : le caractère est dans les raw_keys
|
||||
# du prochain text_input. Extraire depuis les raw_keys de cet event.
|
||||
raw_keys = evt.get("raw_keys", [])
|
||||
for rk in raw_keys:
|
||||
ch = rk.get("char", "")
|
||||
if ch and len(ch) == 1 and ch.isprintable() and rk.get("action") == "release":
|
||||
printable = ch
|
||||
break
|
||||
if printable:
|
||||
# Transformer en text_input pour fusion
|
||||
evt = dict(evt, type="text_input", text=printable)
|
||||
@@ -1022,10 +1031,14 @@ def build_replay_from_raw_events(
|
||||
)
|
||||
elif reconstructed and len(reconstructed) < len(original):
|
||||
# Longueur différente → des chars viennent de key_combos convertis
|
||||
# Garder le texte original (qui inclut les chars fusionnés)
|
||||
# (ex: @ de AltGr fusionné dans le texte mais absent des raw_keys)
|
||||
# Garder le texte original ET supprimer raw_keys pour forcer le
|
||||
# copier-coller au replay (les raw_keys sont incomplets)
|
||||
del evt["raw_keys"]
|
||||
logger.debug(
|
||||
"Texte non reconstruit (longueur diff) : '%s' (%d) vs '%s' (%d)",
|
||||
original[:50], len(original), reconstructed[:50], len(reconstructed),
|
||||
"Texte corrigé (key_combo fusionné) : '%s' → raw_keys supprimé, "
|
||||
"replay par copier-coller",
|
||||
original[:50],
|
||||
)
|
||||
|
||||
# ── 4. Convertir en actions replay normalisées ──
|
||||
@@ -1168,8 +1181,18 @@ def build_replay_from_raw_events(
|
||||
action["type"] = "type"
|
||||
action["text"] = text
|
||||
# Propager les raw_keys pour le replay exact (solution AZERTY)
|
||||
# SAUF si le texte contient des chars fusionnés depuis key_combos
|
||||
# (ex: @ de AltGr) — dans ce cas les raw_keys sont incomplets
|
||||
# et le replay doit utiliser le copier-coller
|
||||
if evt.get("raw_keys"):
|
||||
reconstructed = _reconstruct_text_from_raw_keys(evt["raw_keys"])
|
||||
if len(reconstructed) >= len(text):
|
||||
action["raw_keys"] = evt["raw_keys"]
|
||||
else:
|
||||
logger.debug(
|
||||
"raw_keys incomplets pour '%s' (recon=%d < text=%d) → copier-coller",
|
||||
text[:30], len(reconstructed), len(text),
|
||||
)
|
||||
|
||||
elif evt_type in ("key_press", "key_combo"):
|
||||
keys = evt.get("keys", [])
|
||||
@@ -1235,6 +1258,19 @@ def build_replay_from_raw_events(
|
||||
if session_dir_path:
|
||||
_attach_expected_screenshots(result, events, session_dir_path)
|
||||
|
||||
# ── 9. Supprimer raw_keys incomplets (chars fusionnés depuis key_combos) ──
|
||||
# Quand le texte contient des caractères venant de key_combos convertis
|
||||
# (ex: @ de AltGr), les raw_keys ne les couvrent pas. Forcer le copier-coller.
|
||||
for action in result:
|
||||
if action.get("type") == "type" and action.get("raw_keys"):
|
||||
recon = _reconstruct_text_from_raw_keys(action["raw_keys"])
|
||||
if len(recon) < len(action.get("text", "")):
|
||||
del action["raw_keys"]
|
||||
logger.debug(
|
||||
"raw_keys supprimés pour '%s' (recon=%d < text=%d)",
|
||||
action["text"][:30], len(recon), len(action["text"]),
|
||||
)
|
||||
|
||||
# Stats visual replay
|
||||
visual_clicks = sum(
|
||||
1 for a in result
|
||||
|
||||
Reference in New Issue
Block a user