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:
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user