feat(vwb): câblage 19 blocs, OCR réel, screenshots ancres, configs déploiement
Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 13s
security-audit / pip-audit (CVE dépendances) (push) Successful in 11s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 13s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped
Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 13s
security-audit / pip-audit (CVE dépendances) (push) Successful in 11s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 13s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped
Dispatch execute_action élargi de 12 à 19 blocs opérationnels : - 4 blocs souris (hover, drag_drop, scroll, focus) avec pyautogui - extract_text via Ollama VLM (remplace stub hardcodé) - 5 blocs ai_* redirigés vers execute_ai_analyze avec prompts adaptés - screenshot_evidence (capture + sauvegarde PNG) - verify_element_exists (détection visuelle CLIP) Import workflows Léa enrichi : - Bridge extrait anchor_image_base64 des edges - Import crée VisualAnchor en DB + fichiers thumbnail sur disque - PropertiesPanel affiche automatiquement les screenshots Frontend : - visual_condition et loop_visual masqués (hidden: true) - Filtre dans ToolPalette pour exclure les blocs cachés Déploiement : - 2 configs agent (TIM Pauline + Dev Windows) avec machine_id unique - 2 workflows démo dans la BDD (batch factures + extraction IA) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -218,6 +218,20 @@ def convert_learned_to_vwb_steps(
|
||||
if target.get("by_text"):
|
||||
vwb_params["target_text"] = target["by_text"]
|
||||
|
||||
# Extraire le screenshot de l'ancre pour la preview dans le VWB
|
||||
anchor_b64 = (
|
||||
target.get("anchor_image_base64")
|
||||
or target.get("screenshot")
|
||||
or action_params.get("anchor_image_base64")
|
||||
)
|
||||
if anchor_b64:
|
||||
vwb_params["_anchor_image_base64"] = anchor_b64
|
||||
bbox = target.get("by_position")
|
||||
if bbox and isinstance(bbox, (list, tuple)) and len(bbox) >= 2:
|
||||
vwb_params["_anchor_bbox"] = {
|
||||
"x_pct": bbox[0], "y_pct": bbox[1]
|
||||
}
|
||||
|
||||
label = _build_step_label(vwb_action_type, vwb_params, from_name, to_name)
|
||||
steps.append({
|
||||
"action_type": vwb_action_type,
|
||||
@@ -229,6 +243,10 @@ def convert_learned_to_vwb_steps(
|
||||
"metadata": edge_meta,
|
||||
})
|
||||
|
||||
# Fusionner les type_text consécutifs et les key_press en combos
|
||||
steps = _merge_consecutive_text_inputs(steps)
|
||||
steps = _merge_consecutive_key_presses(steps)
|
||||
|
||||
# Appliquer le layout serpentin à tous les steps
|
||||
_compute_layout(steps)
|
||||
|
||||
@@ -298,6 +316,79 @@ def _convert_compound_substep(
|
||||
return vwb_type, vwb_params
|
||||
|
||||
|
||||
def _merge_consecutive_text_inputs(
|
||||
steps: List[Dict[str, Any]],
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Fusionne les steps type_text consécutifs en un seul.
|
||||
|
||||
Quand un compound est décomposé lettre par lettre (ex: "bonjour" → 7 steps),
|
||||
cette fonction les recombine en un seul step "Saisir : bonjour".
|
||||
"""
|
||||
if not steps:
|
||||
return steps
|
||||
|
||||
merged = [steps[0]]
|
||||
for step in steps[1:]:
|
||||
prev = merged[-1]
|
||||
if (prev["action_type"] == "type_text"
|
||||
and step["action_type"] == "type_text"):
|
||||
# Concaténer le texte
|
||||
prev_text = prev.get("parameters", {}).get("text", "")
|
||||
curr_text = step.get("parameters", {}).get("text", "")
|
||||
prev["parameters"]["text"] = prev_text + curr_text
|
||||
# Mettre à jour le label
|
||||
combined = prev["parameters"]["text"]
|
||||
prev["label"] = f'Saisir : "{combined}"'
|
||||
else:
|
||||
merged.append(step)
|
||||
|
||||
# Réindexer les ordres
|
||||
for idx, step in enumerate(merged):
|
||||
step["order"] = idx
|
||||
|
||||
return merged
|
||||
|
||||
|
||||
def _merge_consecutive_key_presses(
|
||||
steps: List[Dict[str, Any]],
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Fusionne les key_press / keyboard_shortcut consécutifs portant une seule touche
|
||||
en un seul keyboard_shortcut combo (ex: ctrl puis s → ctrl+s).
|
||||
|
||||
Ne fusionne que les steps keyboard_shortcut consécutifs dont chacun ne porte
|
||||
qu'une seule touche (signe d'un combo décomposé). Les raccourcis déjà composés
|
||||
(keys avec 2+ éléments) ne sont pas touchés.
|
||||
"""
|
||||
if not steps:
|
||||
return steps
|
||||
|
||||
merged = [steps[0]]
|
||||
for step in steps[1:]:
|
||||
prev = merged[-1]
|
||||
if (prev["action_type"] == "keyboard_shortcut"
|
||||
and step["action_type"] == "keyboard_shortcut"):
|
||||
prev_keys = prev.get("parameters", {}).get("keys", [])
|
||||
curr_keys = step.get("parameters", {}).get("keys", [])
|
||||
# Ne fusionner que si chaque step porte exactement 1 touche
|
||||
# (un combo déjà composé comme ["ctrl", "s"] ne doit pas absorber le suivant)
|
||||
if len(curr_keys) == 1 and len(prev_keys) >= 1:
|
||||
# Vérifier que le prev est lui-même issu d'une fusion ou d'une seule touche
|
||||
# On fusionne tant que c'est un enchaînement de touches simples
|
||||
prev["parameters"]["keys"] = prev_keys + curr_keys
|
||||
combo_str = "+".join(prev["parameters"]["keys"])
|
||||
prev["label"] = f"Raccourci : {combo_str}"
|
||||
continue
|
||||
merged.append(step)
|
||||
|
||||
# Réindexer les ordres
|
||||
for idx, step in enumerate(merged):
|
||||
step["order"] = idx
|
||||
|
||||
return merged
|
||||
|
||||
|
||||
def _compute_layout(
|
||||
steps: List[Dict[str, Any]],
|
||||
cols: int = 3,
|
||||
|
||||
Reference in New Issue
Block a user