diff --git a/frontend/src/components/common/ProgressModal.tsx b/frontend/src/components/common/ProgressModal.tsx index e8833026..76b5b585 100644 --- a/frontend/src/components/common/ProgressModal.tsx +++ b/frontend/src/components/common/ProgressModal.tsx @@ -197,7 +197,7 @@ export default function ProgressModal({ const stepCompletionTimersRef = useRef>(new Map()); const visuallyCompletedStepsRef = useRef>(new Set()); const lastProcessedStepLogsHashRef = useRef(''); - const lastProcessedPhaseRef = useRef(''); + const lastVisuallyCompletedCountRef = useRef(0); // Sync ref with state useEffect(() => { @@ -334,9 +334,6 @@ export default function ProgressModal({ const allStepsVisuallyCompleted = steps.length > 0 && steps.every(step => visuallyCompletedSteps.has(step.phase)); - // Track the count of visually completed steps to detect changes without causing infinite loops - const visuallyCompletedCount = visuallyCompletedSteps.size; - // Track step completions with 2-second delay between each step useEffect(() => { if (!isOpen) { @@ -344,13 +341,29 @@ export default function ProgressModal({ setVisuallyCompletedSteps(new Set()); visuallyCompletedStepsRef.current = new Set(); lastProcessedStepLogsHashRef.current = ''; - lastProcessedPhaseRef.current = ''; + lastVisuallyCompletedCountRef.current = 0; stepCompletionTimersRef.current.forEach(timer => clearTimeout(timer)); stepCompletionTimersRef.current.clear(); return; } const phaseOrder = ['INIT', 'PREP', 'AI_CALL', 'PARSE', 'SAVE', 'DONE']; + const currentVisuallyCompletedCount = visuallyCompletedSteps.size; + + // Check if we need to process: + // 1. Backend progress changed (stepLogsHash or currentPhase) + // 2. A step just completed visually (count increased) + const currentHash = `${stepLogsHash}|${currentPhase}`; + const hashChanged = currentHash !== lastProcessedStepLogsHashRef.current; + const countChanged = currentVisuallyCompletedCount > lastVisuallyCompletedCountRef.current; + + if (!hashChanged && !countChanged) { + return; // Nothing changed, skip processing + } + + // Update tracking refs + lastProcessedStepLogsHashRef.current = currentHash; + lastVisuallyCompletedCountRef.current = currentVisuallyCompletedCount; // Process steps sequentially - find the first step that should be completed but isn't visually completed yet for (let index = 0; index < steps.length; index++) { @@ -372,7 +385,6 @@ export default function ProgressModal({ // Only schedule if previous step is completed (or this is first step) if (previousStepCompleted) { // Calculate delay: 2 seconds after previous step visually completed (or 0 for first step) - // For subsequent steps, add 2 seconds delay const delay = previousStep ? 2000 : 0; // Schedule completion @@ -400,7 +412,7 @@ export default function ProgressModal({ stepCompletionTimersRef.current.forEach(timer => clearTimeout(timer)); stepCompletionTimersRef.current.clear(); }; - }, [isOpen, currentPhase, stepLogsHash, steps, stepLogs, visuallyCompletedCount]); // Use count instead of Set to avoid infinite loops + }, [isOpen, currentPhase, stepLogsHash, steps, stepLogs, visuallyCompletedSteps.size]); // Use .size instead of the Set // Don't auto-close - user must click close button