Feat: Actions survol_element et glisser_deposer (français)
Nouvelles actions avec nommage français pour l'interface: - survol_element: Survol avec durée configurable et humanisation - glisser_deposer: Drag & drop entre deux ancres visuelles Support des profils français: rapide, normal, lent, furtif Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Actions Vision UI VWB - Module d'initialisation
|
||||
|
||||
Auteur : Dom, Claude - 14 janvier 2026
|
||||
|
||||
Ce module contient les actions d'interface utilisateur basées sur la vision
|
||||
pour le Visual Workflow Builder.
|
||||
|
||||
Actions disponibles :
|
||||
- VWBClickAnchorAction : Clic sur ancre visuelle
|
||||
- VWBTypeTextAction : Saisie de texte
|
||||
- VWBWaitForAnchorAction : Attente d'ancre visuelle
|
||||
- VWBFocusAnchorAction : Donner le focus à un élément
|
||||
- VWBTypeSecretAction : Saisie sécurisée (mots de passe)
|
||||
- VWBScrollToAnchorAction : Défiler vers un élément
|
||||
- VWBExtractTextAction : Extraire du texte d'une zone
|
||||
- VWBSurvolElementAction : Survol d'élément (hover)
|
||||
- VWBGlisserDeposerAction : Glisser-déposer (drag & drop)
|
||||
"""
|
||||
|
||||
from .click_anchor import VWBClickAnchorAction
|
||||
from .type_text import VWBTypeTextAction
|
||||
from .wait_for_anchor import VWBWaitForAnchorAction
|
||||
from .focus_anchor import VWBFocusAnchorAction
|
||||
from .type_secret import VWBTypeSecretAction
|
||||
from .scroll_to_anchor import VWBScrollToAnchorAction
|
||||
from .extract_text import VWBExtractTextAction
|
||||
from .survol_element import VWBSurvolElementAction
|
||||
from .glisser_deposer import VWBGlisserDeposerAction
|
||||
|
||||
__all__ = [
|
||||
'VWBClickAnchorAction',
|
||||
'VWBTypeTextAction',
|
||||
'VWBWaitForAnchorAction',
|
||||
'VWBFocusAnchorAction',
|
||||
'VWBTypeSecretAction',
|
||||
'VWBScrollToAnchorAction',
|
||||
'VWBExtractTextAction',
|
||||
'VWBSurvolElementAction',
|
||||
'VWBGlisserDeposerAction',
|
||||
]
|
||||
|
||||
__version__ = '1.2.0'
|
||||
__author__ = 'Dom, Claude'
|
||||
__date__ = '14 janvier 2026'
|
||||
@@ -0,0 +1,345 @@
|
||||
"""
|
||||
Action Glisser-Déposer - Déplacer un élément vers une destination
|
||||
Auteur : Dom, Claude - 14 janvier 2026
|
||||
|
||||
Cette action permet de glisser un élément source vers une destination,
|
||||
tous deux identifiés visuellement.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from ..base_action import BaseVWBAction, VWBActionResult, VWBActionStatus
|
||||
from ...contracts.error import VWBErrorType, create_vwb_error
|
||||
from ...contracts.visual_anchor import VWBVisualAnchor
|
||||
|
||||
|
||||
class VWBGlisserDeposerAction(BaseVWBAction):
|
||||
"""
|
||||
Action de glisser-déposer entre deux éléments visuels.
|
||||
|
||||
Permet de :
|
||||
- Glisser un élément source vers une destination
|
||||
- Réorganiser des listes
|
||||
- Déplacer des fichiers
|
||||
- Associer des éléments par drag & drop
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_id: str,
|
||||
parameters: Dict[str, Any],
|
||||
screen_capturer=None
|
||||
):
|
||||
"""
|
||||
Initialise l'action de glisser-déposer.
|
||||
|
||||
Args:
|
||||
action_id: Identifiant unique de l'action
|
||||
parameters: Paramètres incluant source et destination
|
||||
screen_capturer: Instance du ScreenCapturer (optionnel)
|
||||
"""
|
||||
super().__init__(
|
||||
action_id=action_id,
|
||||
name="Glisser-Déposer",
|
||||
description="Glisse un élément vers une destination",
|
||||
parameters=parameters,
|
||||
screen_capturer=screen_capturer
|
||||
)
|
||||
|
||||
# Éléments source et destination
|
||||
self.ancre_source: Optional[VWBVisualAnchor] = (
|
||||
parameters.get('ancre_source') or
|
||||
parameters.get('source_anchor') or
|
||||
parameters.get('visual_anchor')
|
||||
)
|
||||
self.ancre_destination: Optional[VWBVisualAnchor] = (
|
||||
parameters.get('ancre_destination') or
|
||||
parameters.get('destination_anchor')
|
||||
)
|
||||
|
||||
# Décalages optionnels
|
||||
self.decalage_source_x = parameters.get('decalage_source_x', parameters.get('source_offset_x', 0))
|
||||
self.decalage_source_y = parameters.get('decalage_source_y', parameters.get('source_offset_y', 0))
|
||||
self.decalage_dest_x = parameters.get('decalage_dest_x', parameters.get('dest_offset_x', 0))
|
||||
self.decalage_dest_y = parameters.get('decalage_dest_y', parameters.get('dest_offset_y', 0))
|
||||
|
||||
# Timing
|
||||
self.delai_avant_glisser_ms = parameters.get('delai_avant_glisser_ms', parameters.get('hold_before_drag_ms', 100))
|
||||
self.duree_glissement_ms = parameters.get('duree_glissement_ms', parameters.get('drag_duration_ms', 500))
|
||||
self.delai_avant_deposer_ms = parameters.get('delai_avant_deposer_ms', parameters.get('hold_before_drop_ms', 100))
|
||||
|
||||
# Configuration
|
||||
self.seuil_confiance = parameters.get('seuil_confiance', parameters.get('confidence_threshold', 0.7))
|
||||
|
||||
# Humanisation
|
||||
self.humaniser = parameters.get('humaniser', parameters.get('humanize', True))
|
||||
self.profil_humain = parameters.get('profil_humain', parameters.get('humanize_profile', 'normal'))
|
||||
|
||||
def validate_parameters(self) -> List[str]:
|
||||
"""Valide les paramètres de l'action."""
|
||||
erreurs = []
|
||||
|
||||
if not self.ancre_source:
|
||||
erreurs.append("Ancre source requise")
|
||||
|
||||
if not self.ancre_destination:
|
||||
erreurs.append("Ancre destination requise")
|
||||
|
||||
if self.duree_glissement_ms < 100:
|
||||
erreurs.append("Durée de glissement minimum: 100ms")
|
||||
|
||||
if self.duree_glissement_ms > 10000:
|
||||
erreurs.append("Durée de glissement maximum: 10 secondes")
|
||||
|
||||
return erreurs
|
||||
|
||||
def execute_core(self, step_id: str) -> VWBActionResult:
|
||||
"""
|
||||
Exécute le glisser-déposer.
|
||||
|
||||
Args:
|
||||
step_id: Identifiant de l'étape
|
||||
|
||||
Returns:
|
||||
Résultat d'exécution
|
||||
"""
|
||||
start_time = datetime.now()
|
||||
|
||||
try:
|
||||
# Trouver la source
|
||||
pos_source = self._trouver_element(self.ancre_source)
|
||||
if not pos_source:
|
||||
return self._create_error_result(
|
||||
step_id=step_id,
|
||||
start_time=start_time,
|
||||
error_type=VWBErrorType.ELEMENT_NOT_FOUND,
|
||||
message="Élément source non trouvé"
|
||||
)
|
||||
|
||||
# Trouver la destination
|
||||
pos_dest = self._trouver_element(self.ancre_destination)
|
||||
if not pos_dest:
|
||||
return self._create_error_result(
|
||||
step_id=step_id,
|
||||
start_time=start_time,
|
||||
error_type=VWBErrorType.ELEMENT_NOT_FOUND,
|
||||
message="Élément destination non trouvé"
|
||||
)
|
||||
|
||||
# Appliquer les décalages
|
||||
source_x = pos_source[0] + self.decalage_source_x
|
||||
source_y = pos_source[1] + self.decalage_source_y
|
||||
dest_x = pos_dest[0] + self.decalage_dest_x
|
||||
dest_y = pos_dest[1] + self.decalage_dest_y
|
||||
|
||||
# Effectuer le glisser-déposer
|
||||
succes = self._glisser_deposer(source_x, source_y, dest_x, dest_y)
|
||||
|
||||
if not succes:
|
||||
return self._create_error_result(
|
||||
step_id=step_id,
|
||||
start_time=start_time,
|
||||
error_type=VWBErrorType.SYSTEM_ERROR,
|
||||
message="Échec du glisser-déposer"
|
||||
)
|
||||
|
||||
end_time = datetime.now()
|
||||
execution_time = (end_time - start_time).total_seconds() * 1000
|
||||
|
||||
nom_source = self._get_nom_ancre(self.ancre_source)
|
||||
nom_dest = self._get_nom_ancre(self.ancre_destination)
|
||||
print(f"🖱️ Glissé '{nom_source}' vers '{nom_dest}'")
|
||||
print(f" De ({source_x}, {source_y}) à ({dest_x}, {dest_y})")
|
||||
|
||||
return VWBActionResult(
|
||||
action_id=self.action_id,
|
||||
step_id=step_id,
|
||||
status=VWBActionStatus.SUCCESS,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
execution_time_ms=execution_time,
|
||||
output_data={
|
||||
'source': {'x': source_x, 'y': source_y, 'nom': nom_source},
|
||||
'destination': {'x': dest_x, 'y': dest_y, 'nom': nom_dest},
|
||||
'duree_glissement_ms': self.duree_glissement_ms
|
||||
},
|
||||
evidence_list=self.evidence_list.copy()
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return self._create_error_result(
|
||||
step_id=step_id,
|
||||
start_time=start_time,
|
||||
error_type=VWBErrorType.SYSTEM_ERROR,
|
||||
message=f"Erreur: {str(e)}",
|
||||
technical_details={'exception': str(e)}
|
||||
)
|
||||
|
||||
def _trouver_element(self, ancre) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
Trouve la position d'un élément sur l'écran.
|
||||
|
||||
Args:
|
||||
ancre: Ancre visuelle à rechercher
|
||||
|
||||
Returns:
|
||||
Tuple (x, y) ou None si non trouvé
|
||||
"""
|
||||
if not ancre:
|
||||
return None
|
||||
|
||||
try:
|
||||
from ...catalog_routes import find_visual_anchor_on_screen
|
||||
|
||||
image_ancre = None
|
||||
bounding_box = None
|
||||
|
||||
if isinstance(ancre, dict):
|
||||
image_ancre = ancre.get('screenshot') or ancre.get('image_base64')
|
||||
bounding_box = ancre.get('bounding_box')
|
||||
elif isinstance(ancre, VWBVisualAnchor):
|
||||
image_ancre = ancre.screenshot_base64
|
||||
if ancre.has_bounding_box():
|
||||
bounding_box = ancre.bounding_box
|
||||
|
||||
# Recherche visuelle
|
||||
if image_ancre:
|
||||
resultat = find_visual_anchor_on_screen(
|
||||
anchor_image_base64=image_ancre,
|
||||
confidence_threshold=self.seuil_confiance,
|
||||
bounding_box=bounding_box
|
||||
)
|
||||
|
||||
if resultat and resultat.get('found'):
|
||||
return (resultat.get('center_x', 0), resultat.get('center_y', 0))
|
||||
|
||||
# Fallback sur coordonnées statiques
|
||||
if bounding_box:
|
||||
x = int(bounding_box.get('x', 0) + bounding_box.get('width', 0) / 2)
|
||||
y = int(bounding_box.get('y', 0) + bounding_box.get('height', 0) / 2)
|
||||
return (x, y)
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Erreur recherche: {e}")
|
||||
return None
|
||||
|
||||
def _glisser_deposer(self, src_x: int, src_y: int, dst_x: int, dst_y: int) -> bool:
|
||||
"""
|
||||
Effectue le glisser-déposer.
|
||||
|
||||
Args:
|
||||
src_x, src_y: Position source
|
||||
dst_x, dst_y: Position destination
|
||||
|
||||
Returns:
|
||||
True si succès
|
||||
"""
|
||||
try:
|
||||
if self.humaniser:
|
||||
return self._glisser_deposer_humanise(src_x, src_y, dst_x, dst_y)
|
||||
else:
|
||||
return self._glisser_deposer_direct(src_x, src_y, dst_x, dst_y)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur glisser-déposer: {e}")
|
||||
return False
|
||||
|
||||
def _glisser_deposer_humanise(self, src_x: int, src_y: int, dst_x: int, dst_y: int) -> bool:
|
||||
"""Glisser-déposer avec comportement humain."""
|
||||
try:
|
||||
from ...utils.humanizer import Humanizer, HumanProfile
|
||||
import pyautogui
|
||||
|
||||
profils = {
|
||||
'rapide': HumanProfile.FAST,
|
||||
'fast': HumanProfile.FAST,
|
||||
'normal': HumanProfile.NORMAL,
|
||||
'lent': HumanProfile.SLOW,
|
||||
'slow': HumanProfile.SLOW,
|
||||
'furtif': HumanProfile.STEALTH,
|
||||
'stealth': HumanProfile.STEALTH,
|
||||
}
|
||||
profil = profils.get(self.profil_humain, HumanProfile.NORMAL)
|
||||
humanizer = Humanizer(profile=profil)
|
||||
|
||||
# 1. Déplacer vers la source avec mouvement naturel
|
||||
src_x_h, src_y_h = humanizer.humanize_position(src_x, src_y)
|
||||
humanizer.move_to(src_x_h, src_y_h)
|
||||
|
||||
# 2. Petit délai avant de cliquer
|
||||
humanizer.wait(50, 100)
|
||||
|
||||
# 3. Appuyer sur le bouton (mouseDown)
|
||||
pyautogui.mouseDown()
|
||||
|
||||
# 4. Attendre un peu (simuler le "grab")
|
||||
humanizer.wait(self.delai_avant_glisser_ms, int(self.delai_avant_glisser_ms * 1.3))
|
||||
|
||||
# 5. Glisser vers la destination avec courbe naturelle
|
||||
dst_x_h, dst_y_h = humanizer.humanize_position(dst_x, dst_y)
|
||||
duration = self.duree_glissement_ms / 1000
|
||||
|
||||
# Mouvement avec variation de vitesse
|
||||
humanizer.move_to(dst_x_h, dst_y_h, duration=duration)
|
||||
|
||||
# 6. Attendre un peu avant de relâcher
|
||||
humanizer.wait(self.delai_avant_deposer_ms, int(self.delai_avant_deposer_ms * 1.3))
|
||||
|
||||
# 7. Relâcher le bouton (mouseUp)
|
||||
pyautogui.mouseUp()
|
||||
|
||||
return True
|
||||
|
||||
except ImportError as e:
|
||||
print(f"⚠️ Module non disponible: {e}, fallback direct")
|
||||
return self._glisser_deposer_direct(src_x, src_y, dst_x, dst_y)
|
||||
|
||||
def _glisser_deposer_direct(self, src_x: int, src_y: int, dst_x: int, dst_y: int) -> bool:
|
||||
"""Glisser-déposer direct avec pyautogui."""
|
||||
try:
|
||||
import pyautogui
|
||||
|
||||
# Utiliser la fonction drag de pyautogui
|
||||
pyautogui.moveTo(src_x, src_y, duration=0.2)
|
||||
time.sleep(self.delai_avant_glisser_ms / 1000)
|
||||
|
||||
pyautogui.drag(
|
||||
dst_x - src_x,
|
||||
dst_y - src_y,
|
||||
duration=self.duree_glissement_ms / 1000,
|
||||
button='left'
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
print("⚠️ pyautogui non disponible - simulation")
|
||||
return True
|
||||
|
||||
def _get_nom_ancre(self, ancre) -> str:
|
||||
"""Retourne le nom d'une ancre."""
|
||||
if isinstance(ancre, dict):
|
||||
return ancre.get('name', ancre.get('nom', 'Élément'))
|
||||
elif isinstance(ancre, VWBVisualAnchor):
|
||||
return ancre.name
|
||||
return "Élément"
|
||||
|
||||
def get_action_info(self) -> Dict[str, Any]:
|
||||
"""Retourne les informations de l'action."""
|
||||
return {
|
||||
'action_id': self.action_id,
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'type': 'glisser_deposer',
|
||||
'parameters': {
|
||||
'source': self._get_nom_ancre(self.ancre_source),
|
||||
'destination': self._get_nom_ancre(self.ancre_destination),
|
||||
'duree_glissement_ms': self.duree_glissement_ms,
|
||||
'humaniser': self.humaniser
|
||||
},
|
||||
'status': self.current_status.value
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
"""
|
||||
Action Survol Élément - Survoler un élément visuel avec la souris
|
||||
Auteur : Dom, Claude - 14 janvier 2026
|
||||
|
||||
Cette action permet de survoler un élément identifié visuellement,
|
||||
utile pour déclencher des menus contextuels ou des tooltips.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from ..base_action import BaseVWBAction, VWBActionResult, VWBActionStatus
|
||||
from ...contracts.error import VWBErrorType, create_vwb_error
|
||||
from ...contracts.visual_anchor import VWBVisualAnchor
|
||||
|
||||
|
||||
class VWBSurvolElementAction(BaseVWBAction):
|
||||
"""
|
||||
Action de survol d'élément visuel.
|
||||
|
||||
Déplace la souris sur un élément identifié par ancre visuelle
|
||||
et maintient le survol pendant une durée configurable.
|
||||
Utile pour :
|
||||
- Afficher des tooltips
|
||||
- Déclencher des menus au survol
|
||||
- Prévisualiser du contenu
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_id: str,
|
||||
parameters: Dict[str, Any],
|
||||
screen_capturer=None
|
||||
):
|
||||
"""
|
||||
Initialise l'action de survol.
|
||||
|
||||
Args:
|
||||
action_id: Identifiant unique de l'action
|
||||
parameters: Paramètres incluant l'ancre visuelle
|
||||
screen_capturer: Instance du ScreenCapturer (optionnel)
|
||||
"""
|
||||
super().__init__(
|
||||
action_id=action_id,
|
||||
name="Survoler Élément",
|
||||
description="Survole un élément visuel avec la souris",
|
||||
parameters=parameters,
|
||||
screen_capturer=screen_capturer
|
||||
)
|
||||
|
||||
# Paramètres du survol
|
||||
self.ancre_visuelle: Optional[VWBVisualAnchor] = parameters.get('visual_anchor') or parameters.get('ancre_visuelle')
|
||||
self.duree_survol_ms = parameters.get('duree_survol_ms', parameters.get('hover_duration_ms', 1000))
|
||||
self.decalage_x = parameters.get('decalage_x', parameters.get('offset_x', 0))
|
||||
self.decalage_y = parameters.get('decalage_y', parameters.get('offset_y', 0))
|
||||
|
||||
# Configuration
|
||||
self.seuil_confiance = parameters.get('seuil_confiance', parameters.get('confidence_threshold', 0.7))
|
||||
|
||||
# Humanisation
|
||||
self.humaniser = parameters.get('humaniser', parameters.get('humanize', True))
|
||||
self.profil_humain = parameters.get('profil_humain', parameters.get('humanize_profile', 'normal'))
|
||||
|
||||
def validate_parameters(self) -> List[str]:
|
||||
"""Valide les paramètres de l'action."""
|
||||
erreurs = []
|
||||
|
||||
if not self.ancre_visuelle:
|
||||
erreurs.append("Ancre visuelle requise")
|
||||
|
||||
if self.duree_survol_ms < 100:
|
||||
erreurs.append("Durée de survol minimum: 100ms")
|
||||
|
||||
if self.duree_survol_ms > 30000:
|
||||
erreurs.append("Durée de survol maximum: 30 secondes")
|
||||
|
||||
return erreurs
|
||||
|
||||
def execute_core(self, step_id: str) -> VWBActionResult:
|
||||
"""
|
||||
Exécute le survol de l'élément.
|
||||
|
||||
Args:
|
||||
step_id: Identifiant de l'étape
|
||||
|
||||
Returns:
|
||||
Résultat d'exécution
|
||||
"""
|
||||
start_time = datetime.now()
|
||||
|
||||
try:
|
||||
# Trouver l'élément
|
||||
position = self._trouver_element()
|
||||
|
||||
if not position:
|
||||
return self._create_error_result(
|
||||
step_id=step_id,
|
||||
start_time=start_time,
|
||||
error_type=VWBErrorType.ELEMENT_NOT_FOUND,
|
||||
message="Élément non trouvé sur l'écran"
|
||||
)
|
||||
|
||||
x, y = position
|
||||
|
||||
# Appliquer le décalage
|
||||
x += self.decalage_x
|
||||
y += self.decalage_y
|
||||
|
||||
# Effectuer le survol
|
||||
succes = self._survoler(x, y)
|
||||
|
||||
if not succes:
|
||||
return self._create_error_result(
|
||||
step_id=step_id,
|
||||
start_time=start_time,
|
||||
error_type=VWBErrorType.SYSTEM_ERROR,
|
||||
message="Échec du survol"
|
||||
)
|
||||
|
||||
# Maintenir le survol
|
||||
self._attendre_survol()
|
||||
|
||||
end_time = datetime.now()
|
||||
execution_time = (end_time - start_time).total_seconds() * 1000
|
||||
|
||||
nom_ancre = self._get_nom_ancre()
|
||||
print(f"🖱️ Survol effectué sur '{nom_ancre}' à ({x}, {y}) pendant {self.duree_survol_ms}ms")
|
||||
|
||||
return VWBActionResult(
|
||||
action_id=self.action_id,
|
||||
step_id=step_id,
|
||||
status=VWBActionStatus.SUCCESS,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
execution_time_ms=execution_time,
|
||||
output_data={
|
||||
'position_x': x,
|
||||
'position_y': y,
|
||||
'duree_survol_ms': self.duree_survol_ms,
|
||||
'nom_ancre': nom_ancre
|
||||
},
|
||||
evidence_list=self.evidence_list.copy()
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return self._create_error_result(
|
||||
step_id=step_id,
|
||||
start_time=start_time,
|
||||
error_type=VWBErrorType.SYSTEM_ERROR,
|
||||
message=f"Erreur: {str(e)}",
|
||||
technical_details={'exception': str(e)}
|
||||
)
|
||||
|
||||
def _trouver_element(self) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
Trouve la position de l'élément sur l'écran.
|
||||
|
||||
Returns:
|
||||
Tuple (x, y) ou None si non trouvé
|
||||
"""
|
||||
try:
|
||||
from ...catalog_routes import find_visual_anchor_on_screen
|
||||
|
||||
# Obtenir l'image et le bounding box
|
||||
image_ancre = None
|
||||
bounding_box = None
|
||||
|
||||
if isinstance(self.ancre_visuelle, dict):
|
||||
image_ancre = self.ancre_visuelle.get('screenshot') or self.ancre_visuelle.get('image_base64')
|
||||
bounding_box = self.ancre_visuelle.get('bounding_box')
|
||||
elif isinstance(self.ancre_visuelle, VWBVisualAnchor):
|
||||
image_ancre = self.ancre_visuelle.screenshot_base64
|
||||
if self.ancre_visuelle.has_bounding_box():
|
||||
bounding_box = self.ancre_visuelle.bounding_box
|
||||
|
||||
# Recherche visuelle
|
||||
if image_ancre:
|
||||
resultat = find_visual_anchor_on_screen(
|
||||
anchor_image_base64=image_ancre,
|
||||
confidence_threshold=self.seuil_confiance,
|
||||
bounding_box=bounding_box
|
||||
)
|
||||
|
||||
if resultat and resultat.get('found'):
|
||||
return (resultat.get('center_x', 0), resultat.get('center_y', 0))
|
||||
|
||||
# Fallback sur coordonnées statiques
|
||||
if bounding_box:
|
||||
x = int(bounding_box.get('x', 0) + bounding_box.get('width', 0) / 2)
|
||||
y = int(bounding_box.get('y', 0) + bounding_box.get('height', 0) / 2)
|
||||
return (x, y)
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Erreur recherche: {e}")
|
||||
return None
|
||||
|
||||
def _survoler(self, x: int, y: int) -> bool:
|
||||
"""
|
||||
Déplace la souris vers la position.
|
||||
|
||||
Args:
|
||||
x: Position X
|
||||
y: Position Y
|
||||
|
||||
Returns:
|
||||
True si succès
|
||||
"""
|
||||
try:
|
||||
if self.humaniser:
|
||||
from ...utils.humanizer import Humanizer, HumanProfile
|
||||
|
||||
profils = {
|
||||
'rapide': HumanProfile.FAST,
|
||||
'fast': HumanProfile.FAST,
|
||||
'normal': HumanProfile.NORMAL,
|
||||
'lent': HumanProfile.SLOW,
|
||||
'slow': HumanProfile.SLOW,
|
||||
'furtif': HumanProfile.STEALTH,
|
||||
'stealth': HumanProfile.STEALTH,
|
||||
}
|
||||
profil = profils.get(self.profil_humain, HumanProfile.NORMAL)
|
||||
|
||||
humanizer = Humanizer(profile=profil)
|
||||
# Humaniser la position légèrement
|
||||
x, y = humanizer.humanize_position(x, y)
|
||||
humanizer.move_to(x, y)
|
||||
return True
|
||||
|
||||
else:
|
||||
import pyautogui
|
||||
pyautogui.moveTo(x, y, duration=0.3)
|
||||
return True
|
||||
|
||||
except ImportError as e:
|
||||
print(f"⚠️ Module non disponible: {e}")
|
||||
return True # Simulation
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur survol: {e}")
|
||||
return False
|
||||
|
||||
def _attendre_survol(self):
|
||||
"""Maintient le survol pendant la durée configurée."""
|
||||
if self.humaniser:
|
||||
try:
|
||||
from ...utils.humanizer import Humanizer, HumanProfile
|
||||
|
||||
humanizer = Humanizer(profile=HumanProfile.NORMAL)
|
||||
humanizer.wait(self.duree_survol_ms, int(self.duree_survol_ms * 1.2))
|
||||
return
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
time.sleep(self.duree_survol_ms / 1000)
|
||||
|
||||
def _get_nom_ancre(self) -> str:
|
||||
"""Retourne le nom de l'ancre."""
|
||||
if isinstance(self.ancre_visuelle, dict):
|
||||
return self.ancre_visuelle.get('name', self.ancre_visuelle.get('nom', 'Élément'))
|
||||
elif isinstance(self.ancre_visuelle, VWBVisualAnchor):
|
||||
return self.ancre_visuelle.name
|
||||
return "Élément"
|
||||
|
||||
def get_action_info(self) -> Dict[str, Any]:
|
||||
"""Retourne les informations de l'action."""
|
||||
return {
|
||||
'action_id': self.action_id,
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'type': 'survol_element',
|
||||
'parameters': {
|
||||
'nom_ancre': self._get_nom_ancre(),
|
||||
'duree_survol_ms': self.duree_survol_ms,
|
||||
'decalage': {'x': self.decalage_x, 'y': self.decalage_y},
|
||||
'humaniser': self.humaniser
|
||||
},
|
||||
'status': self.current_status.value
|
||||
}
|
||||
Reference in New Issue
Block a user