From 360c33660873649a781c9d20a709275dc0f3309a Mon Sep 17 00:00:00 2001 From: Dom Date: Wed, 14 Jan 2026 23:40:55 +0100 Subject: [PATCH] =?UTF-8?q?Fix:=20Ajout=20de=20toutes=20les=20actions=20VW?= =?UTF-8?q?B=20=C3=A0=20la=20liste=20de=20d=C3=A9tection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mise à jour KNOWN_VWB_ACTIONS pour inclure: - double_click_anchor, right_click_anchor, hover_anchor, drag_drop_anchor - keyboard_shortcut - ai_analyze_text - extract_table, download_to_folder - db_save_data, db_read_data - visual_condition, loop_visual Corrige le problème "Type d'étape non reconnu" pour ces actions. Co-Authored-By: Claude Opus 4.5 --- .../src/hooks/useVWBStepIntegration.ts | 418 ++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 visual_workflow_builder/frontend/src/hooks/useVWBStepIntegration.ts diff --git a/visual_workflow_builder/frontend/src/hooks/useVWBStepIntegration.ts b/visual_workflow_builder/frontend/src/hooks/useVWBStepIntegration.ts new file mode 100644 index 000000000..efb29bb74 --- /dev/null +++ b/visual_workflow_builder/frontend/src/hooks/useVWBStepIntegration.ts @@ -0,0 +1,418 @@ +/** + * Hook d'intégration des étapes VWB - Gestion des actions du catalogue dans le workflow + * Auteur : Dom, Alice, Kiro - 12 janvier 2026 + * + * Ce hook gère l'intégration complète des actions VisionOnly du catalogue + * dans le système de workflow, incluant la création, la configuration et l'exécution. + * + * Version 2.0 - Intégration avec StepTypeResolver pour une détection robuste + */ + +import { useState, useCallback, useMemo, useRef } from 'react'; +import { catalogService } from '../services/catalogService'; +import { VWBCatalogAction } from '../types/catalog'; +import { Step, StepType, StepData, StepExecutionState } from '../types'; +import { stepTypeResolver, StepTypeResolutionResult } from '../services/StepTypeResolver'; +// Import du catalogue statique pour fallback direct +import { getStaticActionById, findActionWithFallback } from '../data/staticCatalog'; + +interface VWBStepIntegrationState { + vwbActions: Map; + isLoading: boolean; + error: string | null; + resolutionCache: Map; + lastCacheUpdate: number; +} + +interface VWBStepIntegrationMethods { + createVWBStep: (actionId: string, position: { x: number; y: number }) => Promise; + isVWBAction: (stepType: string) => boolean; + getVWBAction: (actionId: string) => VWBCatalogAction | null; + loadVWBAction: (actionId: string) => Promise; + validateVWBStep: (step: Step) => Promise; + convertDragDataToVWBStep: (dragData: string, position: { x: number; y: number }) => Promise; + resolveStepType: (step: Step) => Promise; + invalidateResolutionCache: () => void; +} + +/** + * Hook pour l'intégration des étapes VWB dans le workflow + * Version 2.0 avec intégration StepTypeResolver + */ +export const useVWBStepIntegration = (): { + state: VWBStepIntegrationState; + methods: VWBStepIntegrationMethods; +} => { + const [vwbActions, setVwbActions] = useState>(new Map()); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [resolutionCache, setResolutionCache] = useState>(new Map()); + const [lastCacheUpdate, setLastCacheUpdate] = useState(Date.now()); + + // Références pour optimisation + const loadingActionsRef = useRef>(new Set()); + const debounceTimeoutRef = useRef | undefined>(undefined); + + // Créer une étape VWB à partir d'une action du catalogue + const createVWBStep = useCallback(async ( + actionId: string, + position: { x: number; y: number } + ): Promise => { + setIsLoading(true); + setError(null); + + try { + // Charger les détails de l'action depuis le catalogue + const action = await catalogService.getActionDetails(actionId); + if (!action) { + throw new Error(`Action VWB non trouvée : ${actionId}`); + } + + // Mettre en cache l'action + setVwbActions(prev => new Map(prev).set(actionId, action)); + + // Créer les paramètres par défaut + const defaultParameters: Record = {}; + Object.entries(action.parameters).forEach(([paramName, paramConfig]) => { + if (paramConfig.default !== undefined) { + defaultParameters[paramName] = paramConfig.default; + } + }); + + // Créer les données de l'étape + const stepData: StepData = { + label: action.name, + stepType: actionId as StepType, // Utiliser l'ID de l'action comme type + parameters: defaultParameters, + isVWBCatalogAction: true, + vwbActionId: actionId, + }; + + // Créer l'étape complète + const step: Step = { + id: `vwb_step_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, + type: actionId as StepType, + name: action.name, + position, + data: stepData, + executionState: StepExecutionState.IDLE, + validationErrors: [], + }; + + return step; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Erreur lors de la création de l\'étape VWB'; + setError(errorMessage); + console.error('Erreur lors de la création de l\'étape VWB:', err); + return null; + } finally { + setIsLoading(false); + } + }, []); + + // Vérifier si un type d'étape correspond à une action VWB (utilise StepTypeResolver) + const isVWBAction = useCallback((stepType: string): boolean => { + try { + // Créer une étape temporaire pour la détection + const tempStep: Step = { + id: 'temp', + type: stepType as StepType, + name: 'temp', + position: { x: 0, y: 0 }, + data: { + label: 'temp', + stepType: stepType as StepType, + parameters: {} + }, + executionState: StepExecutionState.IDLE, + validationErrors: [] + }; + + // Utiliser le StepTypeResolver pour une détection robuste + return stepTypeResolver.isVWBAction(tempStep); + } catch (error) { + console.error('❌ [useVWBStepIntegration] Erreur détection VWB:', error); + + // Fallback vers l'ancienne logique + return vwbActions.has(stepType) || + stepType.startsWith('vwb_') || + stepType.includes('catalog_') || + stepType.includes('_anchor') || + stepType.includes('_text'); + } + }, [vwbActions]); + + // Obtenir une action VWB depuis le cache + const getVWBAction = useCallback((actionId: string): VWBCatalogAction | null => { + return vwbActions.get(actionId) || null; + }, [vwbActions]); + + // Charger une action VWB - Priorité au catalogue statique (instantané) + const loadVWBAction = useCallback(async (actionId: string): Promise => { + // Vérifier d'abord le cache mémoire + const cachedAction = vwbActions.get(actionId); + if (cachedAction) { + console.log('📦 [useVWBStepIntegration] Action trouvée en cache:', actionId); + return cachedAction; + } + + // Éviter les chargements multiples simultanés + if (loadingActionsRef.current.has(actionId)) { + // Attendre brièvement que le chargement en cours se termine (max 500ms) + return new Promise((resolve) => { + let attempts = 0; + const checkInterval = setInterval(() => { + attempts++; + if (!loadingActionsRef.current.has(actionId) || attempts > 10) { + clearInterval(checkInterval); + resolve(vwbActions.get(actionId) || null); + } + }, 50); + }); + } + + loadingActionsRef.current.add(actionId); + setIsLoading(true); + + try { + // PRIORITÉ 1: Catalogue statique (instantané, pas de réseau) + let staticAction = getStaticActionById(actionId); + + // Si pas trouvé exactement, essayer avec recherche fuzzy + if (!staticAction) { + staticAction = findActionWithFallback(actionId); + } + + if (staticAction) { + setVwbActions(prev => new Map(prev).set(actionId, staticAction as VWBCatalogAction)); + console.log('✅ [useVWBStepIntegration] Action VWB chargée (statique):', actionId, staticAction.name); + setError(null); + return staticAction as VWBCatalogAction; + } + + // PRIORITÉ 2: Service catalogue (si statique n'a pas trouvé) + // Avec timeout court pour ne pas bloquer l'UI + try { + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), 1500) + ); + const servicePromise = catalogService.getActionDetails(actionId); + + const action = await Promise.race([servicePromise, timeoutPromise]); + if (action) { + setVwbActions(prev => new Map(prev).set(actionId, action)); + console.log('✅ [useVWBStepIntegration] Action VWB chargée (service):', actionId); + return action; + } + } catch (serviceErr) { + console.warn('⚠️ [useVWBStepIntegration] Service catalogue indisponible:', actionId); + } + + console.warn('⚠️ [useVWBStepIntegration] Action VWB non trouvée:', actionId); + setError(`Action VWB non trouvée: ${actionId}`); + return null; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Erreur de chargement'; + console.error('❌ [useVWBStepIntegration] Erreur chargement action:', errorMessage); + setError(`Erreur chargement action ${actionId}: ${errorMessage}`); + return null; + } finally { + loadingActionsRef.current.delete(actionId); + setIsLoading(false); + } + }, [vwbActions]); + + // Valider une étape VWB + const validateVWBStep = useCallback(async (step: Step): Promise => { + if (!step.data.isVWBCatalogAction || !step.data.vwbActionId) { + return true; // Pas une étape VWB, validation OK + } + + try { + const result = await catalogService.validateAction({ + type: step.data.vwbActionId, + parameters: step.data.parameters, + }); + + return result.is_valid; + } catch (err) { + console.error('Erreur lors de la validation de l\'étape VWB:', err); + return false; + } + }, []); + + // Résoudre le type d'une étape avec le StepTypeResolver + const resolveStepType = useCallback(async (step: Step): Promise => { + const cacheKey = `${step.type}_${step.data?.vwbActionId || 'none'}`; + + // Vérifier le cache local + const cached = resolutionCache.get(cacheKey); + if (cached && Date.now() - cached.timestamp < 30000) { // Cache 30 secondes + return cached; + } + + try { + const result = await stepTypeResolver.resolveParameterConfig(step, { + enableCache: true, + enableLogging: process.env.NODE_ENV === 'development', + fallbackToEmpty: true + }); + + // Mettre en cache le résultat + setResolutionCache(prev => new Map(prev).set(cacheKey, result)); + setLastCacheUpdate(Date.now()); + + return result; + } catch (error) { + console.error('❌ [useVWBStepIntegration] Erreur résolution type:', error); + + // Fallback basique + const fallbackResult: StepTypeResolutionResult = { + stepType: step.type as string, + isVWBAction: isVWBAction(step.type as string), + isStandardType: false, + parameterConfig: [], + detectionMethods: { fallback: true }, + resolutionSource: 'fallback', + timestamp: Date.now() + }; + + return fallbackResult; + } + }, [resolutionCache, isVWBAction]); + + // Invalider le cache de résolution + const invalidateResolutionCache = useCallback(() => { + setResolutionCache(new Map()); + setLastCacheUpdate(Date.now()); + stepTypeResolver.invalidateCache(); + console.log('🗑️ [useVWBStepIntegration] Cache de résolution invalidé'); + }, []); + // Convertir les données de drag-and-drop en étape VWB + const convertDragDataToVWBStep = useCallback(async ( + dragData: string, + position: { x: number; y: number } + ): Promise => { + // Vérifier si c'est une action du catalogue + if (dragData.startsWith('catalog:')) { + const actionId = dragData.replace('catalog:', ''); + return await createVWBStep(actionId, position); + } + + // Vérifier si c'est une action VWB directe + if (isVWBAction(dragData)) { + return await createVWBStep(dragData, position); + } + + return null; // Pas une action VWB + }, [createVWBStep, isVWBAction]); + + // État exposé (mémorisé pour éviter les re-renders inutiles) + const state: VWBStepIntegrationState = useMemo(() => ({ + vwbActions, + isLoading, + error, + resolutionCache, + lastCacheUpdate, + }), [vwbActions, isLoading, error, resolutionCache, lastCacheUpdate]); + + // Méthodes exposées (mémorisées pour éviter les boucles infinies dans les useEffect) + const methods: VWBStepIntegrationMethods = useMemo(() => ({ + createVWBStep, + isVWBAction, + getVWBAction, + loadVWBAction, + validateVWBStep, + convertDragDataToVWBStep, + resolveStepType, + invalidateResolutionCache, + }), [ + createVWBStep, + isVWBAction, + getVWBAction, + loadVWBAction, + validateVWBStep, + convertDragDataToVWBStep, + resolveStepType, + invalidateResolutionCache, + ]); + + return { state, methods }; +}; + +// Liste des actions VWB connues pour détection basée sur le type +const KNOWN_VWB_ACTIONS = [ + // Vision UI + 'click_anchor', 'double_click_anchor', 'right_click_anchor', + 'hover_anchor', 'drag_drop_anchor', + 'type_text', 'type_secret', 'wait_for_anchor', + 'extract_text', 'screenshot_evidence', 'scroll_to_anchor', + 'focus_anchor', 'hotkey', 'keyboard_shortcut', + // Navigation + 'navigate_to_url', 'browser_back', + // Validation + 'verify_element_exists', 'verify_text_content', + // Intelligence + 'ai_analyze_text', + // Data + 'extract_table', 'download_to_folder', + // Database + 'db_save_data', 'db_read_data', + // Control + 'visual_condition', 'loop_visual' +]; + +/** + * Fonction utilitaire pour détecter si un type est une action VWB + */ +const isVWBActionType = (stepType: string): boolean => { + if (KNOWN_VWB_ACTIONS.includes(stepType)) return true; + if (stepType.includes('_anchor')) return true; + if (stepType.includes('_text') && stepType !== 'type') return true; + if (stepType.includes('_secret')) return true; + if (stepType.startsWith('vwb_')) return true; + if (stepType.includes('catalog_')) return true; + return false; +}; + +/** + * Hook utilitaire pour vérifier si une étape est une action VWB + */ +export const useIsVWBStep = (step: Step | null): boolean => { + return useMemo(() => { + if (!step) return false; + return Boolean( + step.data.isVWBCatalogAction || + step.data.vwbActionId || + isVWBActionType(step.type as string) + ); + }, [step]); +}; + +/** + * Hook utilitaire pour obtenir l'ID de l'action VWB d'une étape + * Amélioration: détecte aussi les actions VWB basées sur le type même sans flag + */ +export const useVWBActionId = (step: Step | null): string | null => { + return useMemo(() => { + if (!step) return null; + + // Si le flag est explicitement défini, l'utiliser + if (step.data.isVWBCatalogAction) { + return step.data.vwbActionId || step.type; + } + + // Si vwbActionId est défini, l'utiliser + if (step.data.vwbActionId) { + return step.data.vwbActionId; + } + + // Fallback: détecter basé sur le type d'étape + if (isVWBActionType(step.type as string)) { + console.log('🔍 [useVWBActionId] Détection VWB par type:', step.type); + return step.type; + } + + return null; + }, [step]); +}; \ No newline at end of file