fix(vwb): Corriger auto-save et coordonnées miniature

- Ajouter méthode updateWorkflow (PUT) dans apiClient pour les
  workflows existants
- Utiliser PUT au lieu de POST pour l'auto-sauvegarde des workflows
- Ajouter tracking du scale dans VisualSelector pour convertir les
  coordonnées du canvas vers l'image originale
- Corriger le bounding_box pour correspondre aux dimensions réelles
  de l'image capturée

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-01-20 21:38:47 +01:00
parent d2955ec1a1
commit a9a53991bc
3 changed files with 1987 additions and 2 deletions

View File

@@ -6,7 +6,7 @@
* avec sélection visuelle basée sur la vision et terminologie française.
*/
import { useState, useCallback, useMemo, useEffect, Component, ErrorInfo, ReactNode } from 'react';
import { useState, useCallback, useMemo, useEffect, useRef, Component, ErrorInfo, ReactNode } from 'react';
import {
Box,
CssBaseline,
@@ -46,6 +46,9 @@ import VWBIntegrationTest from './components/VWBIntegrationTest';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
import { useResponsiveLayout } from './hooks/useResponsiveLayout';
// Service API pour l'auto-sauvegarde
import { apiClient } from './services/apiClient';
// Import des types partagés
import {
Step,
@@ -184,6 +187,105 @@ function App() {
updatedAt: new Date(),
});
// État pour l'auto-sauvegarde
const [autoSaveStatus, setAutoSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
const autoSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const lastSavedWorkflowRef = useRef<string>('');
const isInitialLoadRef = useRef(true);
// Auto-sauvegarde vers le backend avec debounce
useEffect(() => {
// Ignorer la sauvegarde initiale (premier render)
if (isInitialLoadRef.current) {
isInitialLoadRef.current = false;
lastSavedWorkflowRef.current = JSON.stringify({
steps: workflow.steps,
connections: workflow.connections,
});
return;
}
// Ne pas sauvegarder si le workflow n'a pas d'ID valide (pas encore créé sur le backend)
if (!workflow.id || workflow.id === 'workflow_1' || !workflow.id.startsWith('wf_')) {
return;
}
// Créer une représentation du workflow pour comparer
const currentWorkflowState = JSON.stringify({
steps: workflow.steps,
connections: workflow.connections,
});
// Ne pas sauvegarder si rien n'a changé
if (currentWorkflowState === lastSavedWorkflowRef.current) {
return;
}
// Annuler le timeout précédent
if (autoSaveTimeoutRef.current) {
clearTimeout(autoSaveTimeoutRef.current);
}
// Programmer la sauvegarde avec debounce (2 secondes)
autoSaveTimeoutRef.current = setTimeout(async () => {
try {
setAutoSaveStatus('saving');
console.log('💾 [AutoSave] Sauvegarde automatique en cours...', workflow.id);
// Préparer les données pour l'API (format compatible avec le backend)
const workflowData = {
id: workflow.id,
name: workflow.name,
description: workflow.description || '',
// Champs requis par WorkflowApiData
steps: workflow.steps,
connections: workflow.connections,
variables: workflow.variables || [],
// Format alternatif pour le backend (nodes/edges)
nodes: workflow.steps.map(step => ({
id: step.id,
type: step.type,
position: step.position,
data: {
...step.data,
stepType: step.type,
},
})),
edges: workflow.connections.map(conn => ({
id: conn.id,
source: conn.source,
target: conn.target,
sourceHandle: (conn as any).sourceHandle,
targetHandle: (conn as any).targetHandle,
})),
};
// Utiliser PUT (updateWorkflow) pour les workflows existants
await apiClient.updateWorkflow(workflow.id, workflowData);
lastSavedWorkflowRef.current = currentWorkflowState;
setAutoSaveStatus('saved');
console.log('✅ [AutoSave] Sauvegarde automatique réussie');
// Remettre à idle après 3 secondes
setTimeout(() => setAutoSaveStatus('idle'), 3000);
} catch (error) {
console.error('❌ [AutoSave] Erreur de sauvegarde automatique:', error);
setAutoSaveStatus('error');
// Remettre à idle après 5 secondes en cas d'erreur
setTimeout(() => setAutoSaveStatus('idle'), 5000);
}
}, 2000); // Debounce de 2 secondes
// Cleanup
return () => {
if (autoSaveTimeoutRef.current) {
clearTimeout(autoSaveTimeoutRef.current);
}
};
}, [workflow.steps, workflow.connections, workflow.id, workflow.name, workflow.description, workflow.variables]);
// Configuration de la navigation au clavier
const keyboardNavigation = useKeyboardNavigation({
onStepSelect: (stepId: string) => {
@@ -445,7 +547,30 @@ function App() {
onWorkflowSave={handleWorkflowSave}
/>
</Box>
{/* Indicateur d'auto-sauvegarde */}
{autoSaveStatus !== 'idle' && (
<Box
sx={{
mr: 2,
px: 1.5,
py: 0.5,
borderRadius: 1,
display: 'flex',
alignItems: 'center',
gap: 0.5,
bgcolor: autoSaveStatus === 'saving' ? 'rgba(255,255,255,0.1)' :
autoSaveStatus === 'saved' ? 'rgba(76,175,80,0.2)' :
'rgba(244,67,54,0.2)',
fontSize: '0.75rem',
}}
>
{autoSaveStatus === 'saving' && '💾 Sauvegarde...'}
{autoSaveStatus === 'saved' && '✅ Sauvegardé'}
{autoSaveStatus === 'error' && '❌ Erreur sauvegarde'}
</Box>
)}
{/* Indicateur de connexion API */}
<Box sx={{ mr: 2 }}>
<ConnectionIndicator compact showRefreshButton />