diff --git a/visual_workflow_builder/backend/api_v3/execute.py b/visual_workflow_builder/backend/api_v3/execute.py index 28262e497..666923652 100644 --- a/visual_workflow_builder/backend/api_v3/execute.py +++ b/visual_workflow_builder/backend/api_v3/execute.py @@ -24,23 +24,32 @@ logger = logging.getLogger(__name__) def safe_type_text(text): - """Saisie de texte compatible avec tous les claviers (AZERTY/QWERTY). - - pyautogui.write() envoie des codes de touches QWERTY qui produisent - des caractères erronés sur AZERTY (* → µ, ( → 5, ) → °, etc.). + """Saisie de texte compatible VM/Citrix et claviers AZERTY/QWERTY. Priorité : - 1. Presse-papier (xclip) + Ctrl+V → instantané, fiable pour tout texte - 2. xdotool type → respecte le layout clavier - 3. pyautogui.write() → dernier recours + 1. xdotool type → frappes clavier réelles, traverse QEMU/Citrix/RDP + 2. Presse-papier (xclip) + Ctrl+V → fallback local uniquement + 3. pyautogui.write() → dernier recours """ import shutil import pyautogui - # Méthode 1 : Presse-papier (le plus fiable, gère UTF-8/accents/CJK) - # xclip reste en vie comme daemon X11 pour servir le clipboard. - # On ne doit PAS attendre sa terminaison — juste envoyer les données - # et coller immédiatement. + # Méthode 1 : xdotool type (frappes clavier réelles) + # Envoie des événements X11 que QEMU/Citrix/RDP capturent et transmettent. + # Le presse-papier ne traverse PAS les VM — xdotool si. + if shutil.which('xdotool'): + try: + subprocess.run( + ['xdotool', 'type', '--delay', '20', '--clearmodifiers', '--', text], + timeout=max(30, len(text) * 0.05), + check=True + ) + print(f" ✅ Saisie via xdotool type ({len(text)} car.)") + return + except Exception as e: + print(f" ⚠️ xdotool type échoué: {e}") + + # Méthode 2 : Presse-papier (fonctionne seulement en local, pas en VM) xclip = shutil.which('xclip') if xclip: try: @@ -52,7 +61,6 @@ def safe_type_text(text): ) p.stdin.write(text.encode('utf-8')) p.stdin.close() - # Ne PAS attendre xclip — il reste vivant comme owner du clipboard time.sleep(0.2) pyautogui.hotkey('ctrl', 'v') time.sleep(0.3) @@ -61,19 +69,6 @@ def safe_type_text(text): except Exception as e: print(f" ⚠️ xclip échoué: {e}") - # Méthode 2 : xdotool type (respecte le layout clavier actif) - if shutil.which('xdotool'): - try: - subprocess.run( - ['xdotool', 'type', '--delay', '0', '--clearmodifiers', '--', text], - timeout=max(30, len(text) * 0.05), - check=True - ) - print(f" ✅ Saisie via xdotool type ({len(text)} car.)") - return - except Exception as e: - print(f" ⚠️ xdotool type échoué: {e}") - # Méthode 3 : pyautogui (dernier recours — problèmes AZERTY possibles) print(" ⚠️ Saisie via pyautogui.write() (AZERTY non garanti)") pyautogui.write(text) @@ -87,7 +82,8 @@ def minimize_active_window(): # Minimiser la fenêtre active subprocess.run(['xdotool', 'getactivewindow', 'windowminimize'], capture_output=True, timeout=2) - print("📦 [Execute] Fenêtre du navigateur minimisée") + time.sleep(1.5) + print("📦 [Execute] Fenêtre du navigateur minimisée (+ 1.5s d'attente)") return True except FileNotFoundError: print("⚠️ [Execute] xdotool non installé - impossible de minimiser") @@ -183,7 +179,11 @@ def execute_workflow_thread(execution_id: str, workflow_id: str, app): db.session.add(step_result) db.session.commit() - print(f"📋 [Execute] Étape {index + 1}/{len(steps)}: {step.action_type} (id={step.id})") + print(f"\n{'='*60}") + print(f"📋 [Execute] Étape {index + 1}/{len(steps)}: {step.action_type}") + print(f" step_id={step.id}, label={step.label}") + print(f" anchor_id={step.anchor_id or 'aucune'}") + print(f" mode={_execution_state.get('execution_mode')}") try: # === VALIDATION CONTRAT STRICT === @@ -780,13 +780,19 @@ def execute_action(action_type: str, params: dict) -> dict: execution_mode = _execution_state.get('execution_mode', 'basic') + print(f" [dispatch] action={action_type} mode={execution_mode}") + print(f" [dispatch] params_keys={list(params.keys())}") + try: if action_type in ['click_anchor', 'click', 'double_click_anchor', 'right_click_anchor']: - # Récupérer les coordonnées depuis l'ancre anchor = params.get('visual_anchor', {}) bbox = anchor.get('bounding_box', {}) screenshot_base64 = anchor.get('screenshot') + print(f" [click] bbox={bbox}") + print(f" [click] has_screenshot={bool(screenshot_base64)} ({len(screenshot_base64) if screenshot_base64 else 0} chars)") + print(f" [click] screen_size={pyautogui.size()}") + if not bbox: return {'success': False, 'error': 'Pas de bounding_box dans visual_anchor'} diff --git a/visual_workflow_builder/frontend_v4/src/styles.css b/visual_workflow_builder/frontend_v4/src/styles.css index b7abd9387..a6e55ddc7 100644 --- a/visual_workflow_builder/frontend_v4/src/styles.css +++ b/visual_workflow_builder/frontend_v4/src/styles.css @@ -167,6 +167,7 @@ body { .workflow-list li { display: flex; flex-direction: column; + color: var(--text-primary); padding: 0; cursor: pointer; border-bottom: 1px solid var(--border); @@ -179,6 +180,10 @@ body { .workflow-list li.active { background: var(--primary-light); + color: white !important; +} + +.workflow-list li.active .wf-name { color: white; } @@ -192,6 +197,7 @@ body { .workflow-list .wf-name { flex: 1; font-weight: 500; + color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;