From 14a9442343b0f20ea710377b770c67202b505651 Mon Sep 17 00:00:00 2001 From: Dom Date: Tue, 21 Apr 2026 09:03:19 +0200 Subject: [PATCH] =?UTF-8?q?refactor(vwb):=20refonte=20compl=C3=A8te=20capt?= =?UTF-8?q?ure=20=C3=A9cran=20=E2=80=94=20stable=20d=C3=A9finitivement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FullscreenSelector réécrit : - Overlay unique positionné via getBoundingClientRect() - Recalcul auto au resize - Coordonnées souris relatives à l'image - Plus de décalage bboxes/sélection Capture backend : - mss.monitors[0] (écran composite) au lieu de pyautogui.screenshot() - Capture la VM en plein écran correctement Co-Authored-By: Claude Opus 4.6 (1M context) --- .../backend/api_v3/capture.py | 20 +- .../src/components/CapturePanel.tsx | 264 +++++++++++------- 2 files changed, 185 insertions(+), 99 deletions(-) diff --git a/visual_workflow_builder/backend/api_v3/capture.py b/visual_workflow_builder/backend/api_v3/capture.py index 114dec374..76501987d 100644 --- a/visual_workflow_builder/backend/api_v3/capture.py +++ b/visual_workflow_builder/backend/api_v3/capture.py @@ -52,10 +52,24 @@ def capture_screen(): } """ try: - import pyautogui + # Utiliser mss pour capturer TOUS les moniteurs (ecran compose). + # pyautogui.screenshot() capture uniquement le premier moniteur, + # ce qui rate la VM en plein ecran sur un second ecran ou via QEMU/spice. + # mss.monitors[0] = ecran compose (tous les moniteurs), ce qui capture + # exactement ce que l'utilisateur voit quel que soit le setup. + try: + import mss + with mss.mss() as sct: + # monitors[0] = ecran virtuel englobant tous les moniteurs + monitor = sct.monitors[0] + sct_img = sct.grab(monitor) + # Convertir mss ScreenShot (BGRA) en PIL Image RGB + screenshot = Image.frombytes('RGB', sct_img.size, sct_img.rgb) + except ImportError: + # Fallback pyautogui si mss n'est pas installe + import pyautogui + screenshot = pyautogui.screenshot() - # Capture écran - screenshot = pyautogui.screenshot() width, height = screenshot.size # Convertir en base64 diff --git a/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx b/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx index b6d4c2d71..0cd32c788 100644 --- a/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx +++ b/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect, useCallback } from 'react'; import type { Capture, ExecutionMode } from '../types'; import type { UIElement } from '../services/uiDetection'; import { @@ -7,6 +7,10 @@ import { compressThumbnail, } from '../services/captureLibraryStorage'; +/** + * Auto-detecte PNG vs JPEG depuis le contenu base64 et retourne un data: URL. + * GARDER CETTE FONCTION : elle evite les bugs d'affichage PNG/JPEG. + */ function b64ImgSrc(base64: string): string { if (base64.startsWith('data:')) return base64; const mime = base64.startsWith('/9j/') ? 'image/jpeg' : 'image/png'; @@ -53,7 +57,7 @@ export default function CapturePanel({ const [currentCapture, setCurrentCapture] = useState(null); const [timerSeconds, setTimerSeconds] = useState(0); const [countdown, setCountdown] = useState(null); - // Éléments détectés sur l'aperçu miniature + // Elements detectes sur l'apercu miniature const [previewElements, setPreviewElements] = useState([]); const [isDetectingPreview, setIsDetectingPreview] = useState(false); const previewImgRef = useRef(null); @@ -61,8 +65,7 @@ export default function CapturePanel({ const isDebugMode = executionMode === 'debug'; - // Charger la bibliothèque depuis le backend (prioritaire), fallback localStorage. - // Le helper loadLibraryAsync() migre aussi les données de l'ancienne clé 'captureLibrary'. + // Charger la bibliotheque depuis le backend (prioritaire), fallback localStorage. useEffect(() => { loadLibraryAsync().then((loaded) => { setLibrary( @@ -77,12 +80,12 @@ export default function CapturePanel({ }); }, []); - // Sauvegarder la bibliothèque (localStorage + gestion de quota) + // Sauvegarder la bibliotheque (localStorage + backend) useEffect(() => { saveLibrary(library); }, [library]); - // Ajouter capture à la bibliothèque (thumbnail compressé JPEG 320x240) + // Ajouter capture a la bibliotheque (thumbnail compresse JPEG 320x240) useEffect(() => { if (!capture) return; setCurrentCapture(capture); @@ -101,7 +104,7 @@ export default function CapturePanel({ return () => { cancelled = true; }; }, [capture]); - // Détecter les éléments UI quand une capture arrive + // Detecter les elements UI quand une capture arrive useEffect(() => { if (!currentCapture) { setPreviewElements([]); @@ -118,7 +121,7 @@ export default function CapturePanel({ ); setPreviewElements(result.elements); } catch (err) { - console.error('Détection aperçu échouée:', err); + console.error('Detection apercu echouee:', err); setPreviewElements([]); } finally { setIsDetectingPreview(false); @@ -128,7 +131,7 @@ export default function CapturePanel({ runDetection(); }, [currentCapture]); - // Calculer le scale de l'aperçu quand l'image est chargée + // Calculer le scale de l'apercu quand l'image est chargee const handlePreviewImageLoad = () => { if (previewImgRef.current) { setPreviewScale({ @@ -153,7 +156,6 @@ export default function CapturePanel({ } as any); return; } - // Agent indisponible — fallback capture locale console.warn('Agent Windows indisponible, fallback local:', data.error); } catch (err) { console.warn('Erreur capture Windows, fallback local:', err); @@ -185,7 +187,7 @@ export default function CapturePanel({ const handleLibrarySelect = (item: LibraryItem) => { setCurrentCapture(item.capture); - setIsFullscreen(true); // Ouvrir en plein écran pour sélectionner + setIsFullscreen(true); }; const handleDeleteLibraryItem = (id: string) => { @@ -196,13 +198,13 @@ export default function CapturePanel({

Capture

- {/* Capture — auto-détection OS navigateur */} + {/* Capture — auto-detection OS navigateur */}