diff --git a/core/detection/ui_detector.py b/core/detection/ui_detector.py index 9141ba1ed..de40982e2 100644 --- a/core/detection/ui_detector.py +++ b/core/detection/ui_detector.py @@ -213,7 +213,18 @@ class UIDetector: regions.extend(opencv_regions) logger.debug(f"Total: {len(regions)} candidate regions") - + + # Limiter les candidats AVANT classification VLM pour éviter + # des centaines d'appels VLM inutiles (~2-3s chacun). + # On garde max 80 candidats — suffisant pour obtenir ~50 éléments + # après filtrage par confiance, tout en gardant un temps raisonnable. + max_candidates = 80 + if len(regions) > max_candidates: + # Trier par confiance décroissante, puis par surface décroissante + regions.sort(key=lambda r: (r.confidence, r.w * r.h), reverse=True) + logger.info(f"Pruning {len(regions)} candidates → {max_candidates} (pre-VLM cap)") + regions = regions[:max_candidates] + # Étape 2: Classifier chaque région avec le VLM logger.debug("Step 2: Classifying regions with VLM...") ui_elements = [] diff --git a/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx b/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx index 6ac8c0b67..593e869fd 100644 --- a/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx +++ b/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx @@ -39,6 +39,11 @@ 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 + const [previewElements, setPreviewElements] = useState([]); + const [isDetectingPreview, setIsDetectingPreview] = useState(false); + const previewImgRef = useRef(null); + const [previewScale, setPreviewScale] = useState({ x: 1, y: 1 }); const isDebugMode = executionMode === 'debug'; @@ -68,6 +73,43 @@ export default function CapturePanel({ } }, [capture]); + // Détecter les éléments UI quand une capture arrive + useEffect(() => { + if (!currentCapture) { + setPreviewElements([]); + return; + } + + const runDetection = async () => { + setIsDetectingPreview(true); + try { + const { detectUIElements } = await import('../services/uiDetection'); + const result = await detectUIElements( + `data:image/png;base64,${currentCapture.screenshot_base64}`, + { threshold: 0.35 } + ); + setPreviewElements(result.elements); + } catch (err) { + console.error('Détection aperçu échouée:', err); + setPreviewElements([]); + } finally { + setIsDetectingPreview(false); + } + }; + + runDetection(); + }, [currentCapture]); + + // Calculer le scale de l'aperçu quand l'image est chargée + const handlePreviewImageLoad = () => { + if (previewImgRef.current) { + setPreviewScale({ + x: previewImgRef.current.width / previewImgRef.current.naturalWidth, + y: previewImgRef.current.height / previewImgRef.current.naturalHeight + }); + } + }; + const handleTimerCapture = () => { if (timerSeconds === 0) { onCapture(); @@ -117,16 +159,43 @@ export default function CapturePanel({ - {/* Aperçu de la capture */} + {/* Aperçu de la capture avec détection */} {currentCapture && (
- Capture setIsFullscreen(true)} - /> +
+ Capture setIsFullscreen(true)} + onLoad={handlePreviewImageLoad} + /> + {/* Bounding boxes des éléments détectés sur l'aperçu miniature */} + {previewElements.map((elem) => ( +
+ ))} + {isDetectingPreview && ( +
Détection...
+ )} +

{currentCapture.width}x{currentCapture.height} + {previewElements.length > 0 && ( + + {previewElements.length} éléments + + )}

@@ -139,13 +208,13 @@ export default function CapturePanel({ {/* Zone de détection */} {detectionZone && (
- 📍 Zone de détection: {detectionZone.width}x{detectionZone.height} - + Zone de détection: {detectionZone.width}x{detectionZone.height} +
)} {!detectionZone && currentCapture && (executionMode === 'intelligent' || executionMode === 'debug') && (

- 💡 Cliquez sur "Plein écran" puis "Zone de détection" pour cibler une zone + Cliquez sur "Plein écran" puis "Zone de détection" pour cibler une zone

)} @@ -217,11 +286,10 @@ function FullscreenSelector({ const [isDetecting, setIsDetecting] = useState(false); const [imageScale, setImageScale] = useState({ x: 1, y: 1 }); const [selectionMode, setSelectionMode] = useState<'anchor' | 'zone'>('anchor'); + const [showDetection, setShowDetection] = useState(true); - // Lancer la détection en mode Debug + // Lancer la détection automatiquement (tous modes) useEffect(() => { - if (!debugMode) return; - const runDetection = async () => { setIsDetecting(true); try { @@ -239,7 +307,7 @@ function FullscreenSelector({ }; runDetection(); - }, [debugMode, capture.screenshot_base64]); + }, [capture.screenshot_base64]); // Calculer le scale quand l'image est chargée const handleImageLoad = () => { @@ -339,28 +407,36 @@ function FullscreenSelector({
- {debugMode && isDetecting && '🔍 Détection en cours... '} - {debugMode && !isDetecting && `🎯 ${detectedElements.length} éléments - `} + {isDetecting && 'Détection en cours... '} + {!isDetecting && detectedElements.length > 0 && `${detectedElements.length} éléments - `} {selectionMode === 'zone' - ? '✂️ Dessinez la zone de détection' - : (enabled ? 'Dessinez un rectangle pour l\'ancre' : 'Sélectionnez d\'abord une étape')} + ? 'Dessinez la zone de détection' + : (enabled ? 'Dessinez un rectangle pour l\'ancre ou cliquez sur un élément détecté' : 'Sélectionnez d\'abord une étape')}
+ {/* Bouton afficher/masquer la détection */} + {onSetDetectionZone && ( <> {detectionZone && ( )} @@ -385,8 +461,8 @@ function FullscreenSelector({ style={{ display: 'block' }} /> - {/* Overlay des éléments détectés en mode Debug */} - {debugMode && detectedElements.map((elem) => ( + {/* Overlay des éléments détectés — visible dans tous les modes */} + {showDetection && detectedElements.map((elem) => (
(null); + + // Fermer le tooltip en cliquant à l'extérieur + useEffect(() => { + if (!showHelp) return; + const handleClickOutside = (e: MouseEvent) => { + if (helpRef.current && !helpRef.current.contains(e.target as Node)) { + setShowHelp(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [showHelp]); + return (
+ {/* Bouton aide (?) — toujours visible quand sélectionné */} + {selected && action && ( + + )} + + {/* Tooltip d'aide */} + {showHelp && action && ( +
e.stopPropagation()}> +
+ {action.icon} + {action.label} +
+

{action.description}

+ {action.needsAnchor && ( +
Ancre visuelle requise
+ )} + {action.params.length > 0 && ( +
+
Paramètres :
+ {action.params.map((p) => ( +
+ {p.name} + {p.type} + {p.description} +
+ ))} +
+ )} +
+ )} + {/* Bouton supprimer */} {selected && (