import React, { useState, useEffect, useRef } from "react"; import { Box, Typography, CircularProgress, Alert, Paper } from "@mui/material"; import PlayArrowIcon from "@mui/icons-material/PlayArrow"; import AccessTimeIcon from "@mui/icons-material/AccessTime"; import LogDisplay from "./LogDisplay"; import { useNavigate, useSearchParams } from "react-router-dom"; import API_CONFIG from "../config/api"; // Temps de simulation en millisecondes pour les documents précalculés const SIMULATION_DURATION = 20000; // 20 secondes // Define all benchmark steps in sequence const BENCHMARK_STEPS = [ "configuration", "ingestion", "upload_ingest_to_hub", "summarization", "chunking", "single_shot_question_generation", ]; // Step labels for display (more user-friendly names) const STEP_LABELS = { configuration: "Configuration", ingestion: "Ingestion", upload_ingest_to_hub: "Upload to Hub", summarization: "Summarization", chunking: "Chunking", single_shot_question_generation: "Question generation", }; // Simulated log messages for pre-calculated documents const SIMULATED_LOGS = [ "[INFO] Initializing benchmark generation...", "[INFO] Generating base configuration file...", "[SUCCESS] Stage completed: configuration", "[INFO] Starting ingestion process...", "[SUCCESS] Stage completed: ingestion", "[INFO] Processing document content for upload...", "[SUCCESS] Stage completed: upload_ingest_to_hub", "[INFO] Generating document summary...", "[SUCCESS] Stage completed: summarization", "[INFO] Chunking content for better analysis...", "[SUCCESS] Stage completed: chunking", "[INFO] Generating single-shot questions...", "[SUCCESS] Stage completed: single_shot_question_generation", "[SUCCESS] Benchmark process completed successfully", ]; /** * Component to handle benchmark generation and display logs * * @param {Object} props - Component props * @param {string} props.sessionId - The session ID for the uploaded file * @param {boolean} props.isDefaultDocument - Whether this is a pre-calculated document * @param {Function} props.onComplete - Function to call when generation is complete * @returns {JSX.Element} Benchmark generator component */ const BenchmarkGenerator = ({ sessionId, isDefaultDocument, onComplete }) => { const [searchParams] = useSearchParams(); const isDefault = searchParams.get("isDefault") === "true" || isDefaultDocument; const [generating, setGenerating] = useState(false); const [generationComplete, setGenerationComplete] = useState(false); const [generationLogs, setGenerationLogs] = useState([]); const [error, setError] = useState(null); const [currentPhase, setCurrentPhase] = useState("initializing"); const [completedSteps, setCompletedSteps] = useState([]); const [activeStep, setActiveStep] = useState(1); const [elapsedTime, setElapsedTime] = useState(0); // Reference to keep track of the polling interval const pollingIntervalRef = useRef(null); // Reference to keep track of the timer interval const timerIntervalRef = useRef(null); // Reference for starting time const startTimeRef = useRef(null); // Simulation interval reference const simulationIntervalRef = useRef(null); // Start generation on component mount useEffect(() => { // Set start time startTimeRef.current = Date.now(); // Start timer timerIntervalRef.current = setInterval(() => { const timeElapsed = Math.floor( (Date.now() - startTimeRef.current) / 1000 ); setElapsedTime(timeElapsed); }, 1000); // Gestionnaire pour détecter quand la page redevient visible const handleVisibilityChange = () => { if ( document.visibilityState === "visible" && !isDefault && !generationComplete ) { console.log("Page became visible, checking for missed steps..."); // Force une nouvelle requête pour récupérer les logs const checkCurrentState = async () => { try { // D'abord essayer de récupérer les logs de benchmark const logsResponse = await fetch( `${API_CONFIG.BASE_URL}/benchmark-logs/${sessionId}` ); if (logsResponse.ok) { const logsResult = await logsResponse.json(); if (logsResult.logs) { setGenerationLogs(logsResult.logs); } // Si la tâche est terminée, mettre à jour l'état if (logsResult.is_completed) { setGenerationComplete(true); if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } if (onComplete) { onComplete({ success: true, sessionId, logs: logsResult.logs, }); } } } else { // Si la tâche de benchmark n'existe pas, essayer les logs de configuration const configResponse = await fetch( `${API_CONFIG.BASE_URL}/config-logs/${sessionId}` ); if (configResponse.ok) { const configResult = await configResponse.json(); if (configResult.logs) { setGenerationLogs(configResult.logs); } } } } catch (error) { console.error("Error checking for missed steps:", error); } }; checkCurrentState(); } }; // Ajouter l'écouteur pour le changement de visibilité document.addEventListener("visibilitychange", handleVisibilityChange); if (isDefault) { simulateGeneration(); } else { generateBenchmark(); } // Clean up the polling interval and timer when the component unmounts return () => { if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } if (timerIntervalRef.current) { clearInterval(timerIntervalRef.current); } if (simulationIntervalRef.current) { clearInterval(simulationIntervalRef.current); } document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [isDefault, sessionId, generationComplete, onComplete]); // Simulate the benchmark generation for pre-calculated documents const simulateGeneration = () => { setGenerating(true); setGenerationLogs([]); setError(null); setCurrentPhase("initializing"); setCompletedSteps([]); setActiveStep(1); // Timing variables for simulation const totalSteps = SIMULATED_LOGS.length; const totalDuration = SIMULATION_DURATION; // 20 seconds const intervalPerStep = totalDuration / totalSteps; let currentStep = 0; // Function to add next log message const addNextLog = () => { if (currentStep < SIMULATED_LOGS.length) { const newLogs = [...generationLogs, SIMULATED_LOGS[currentStep]]; setGenerationLogs(newLogs); currentStep++; // Check if completed if (currentStep >= SIMULATED_LOGS.length) { // Simulation complete setTimeout(() => { setCurrentPhase("complete"); setGenerationComplete(true); clearInterval(simulationIntervalRef.current); if (onComplete) { onComplete({ success: true, sessionId, logs: newLogs, }); } }, 1000); } } }; // Start simulation simulationIntervalRef.current = setInterval(addNextLog, intervalPerStep); }; // Determine the current phase and completed steps based on logs useEffect(() => { if (generationLogs.length === 0) return; // Recalculer complètement les étapes complétées à chaque fois // au lieu de simplement ajouter les nouvelles étapes const newCompletedSteps = []; // Identifier toutes les étapes complétées dans tous les logs generationLogs.forEach((log) => { const match = log.match(/\[SUCCESS\] Stage completed: (\w+)/); if (match && match[1]) { const completedStep = match[1].trim(); if ( BENCHMARK_STEPS.includes(completedStep) && !newCompletedSteps.includes(completedStep) ) { newCompletedSteps.push(completedStep); } } }); // Déterminer l'étape active basée sur les étapes complétées let newActiveStep = activeStep; if (newCompletedSteps.length > 0) { // Trouver l'étape la plus avancée dans les logs const maxCompletedStepIndex = Math.max( ...newCompletedSteps.map((step) => BENCHMARK_STEPS.indexOf(step)) ); // Passer à l'étape suivante const calculatedStep = maxCompletedStepIndex + 1; // Ne mettre à jour que si la nouvelle étape est plus avancée que l'étape actuelle if (calculatedStep > activeStep) { newActiveStep = calculatedStep; } // S'assurer que l'activeStep ne dépasse pas le nombre total d'étapes if (newActiveStep >= BENCHMARK_STEPS.length) { newActiveStep = BENCHMARK_STEPS.length; } } else if (activeStep === 0) { // Si aucune étape n'est trouvée et l'étape active est 0, passer à 1 newActiveStep = 1; } // Mettre à jour l'état si les étapes ont changé if (JSON.stringify(newCompletedSteps) !== JSON.stringify(completedSteps)) { setCompletedSteps(newCompletedSteps); } // Mettre à jour l'étape active seulement si elle a changé if (newActiveStep !== activeStep) { setActiveStep(newActiveStep); } // Skip the rest of the log processing if we're simulating if (isDefault) return; // Check the latest logs to determine the current phase const recentLogs = generationLogs.slice(-10); // Check more logs // Detect completion conditions const isComplete = recentLogs.some((log) => log.includes("[SUCCESS] Benchmark process completed successfully") ) || recentLogs.some((log) => log.includes("[SUCCESS] Ingestion process completed successfully") ) || newCompletedSteps.includes("single_shot_question_generation") || newActiveStep >= BENCHMARK_STEPS.length; if (isComplete) { setCurrentPhase("complete"); setGenerationComplete(true); // Stop polling when benchmark is complete if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } // Notify parent component that generation is complete if (onComplete) { console.log("Notifying parent that generation is complete"); onComplete({ success: true, sessionId, logs: generationLogs, }); } } else if ( recentLogs.some((log) => log.includes("Starting ingestion process")) ) { setCurrentPhase("benchmarking"); } else if ( recentLogs.some((log) => log.includes("Generating base configuration")) ) { setCurrentPhase("configuring"); } }, [ generationLogs, completedSteps, activeStep, sessionId, onComplete, isDefault, ]); const generateBenchmark = async () => { if (!sessionId) { setError("Missing session ID"); return; } setGenerating(true); setGenerationLogs([]); setError(null); setCurrentPhase("initializing"); setCompletedSteps([]); setActiveStep(1); try { // Call the API to generate the benchmark const response = await fetch( `${API_CONFIG.BASE_URL}/generate-benchmark`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ session_id: sessionId, }), } ); const result = await response.json(); if (response.ok) { setGenerationLogs(result.logs || []); // Configurer le polling pour suivre la progression pollingIntervalRef.current = setInterval(async () => { // Vérifier si on a déjà terminé if (generationComplete) { clearInterval(pollingIntervalRef.current); return; } try { // Appeler l'API pour obtenir les derniers logs const logsResponse = await fetch( `${API_CONFIG.BASE_URL}/benchmark-progress/${sessionId}` ); if (logsResponse.ok) { const logsResult = await logsResponse.json(); // Mettre à jour les logs s'il y en a de nouveaux if ( logsResult.logs && logsResult.logs.length > generationLogs.length ) { setGenerationLogs(logsResult.logs); } // Vérifier si la tâche est terminée if (logsResult.is_completed) { setGenerationComplete(true); clearInterval(pollingIntervalRef.current); // La notification est maintenant gérée dans le useEffect ci-dessus } } } catch (error) { console.log("Error polling for logs:", error); // Ne pas arrêter le polling en cas d'erreurs réseau } }, 2000); // Interroger toutes les 2 secondes } else { // Handle error setGenerationLogs([`Error: ${result.error || "Unknown error"}`]); setError(result.error || "Benchmark generation failed"); } } catch (error) { console.error("Error generating benchmark:", error); setGenerationLogs([`Error: ${error.message || "Unknown error"}`]); setError("Server connection error"); } finally { setGenerating(false); } }; // Get title based on current phase const getPhaseTitle = () => { switch (currentPhase) { case "initializing": return "Benchmark generation..."; case "configuring": return "Creating benchmark..."; case "benchmarking": return "Creating benchmark..."; case "complete": return "Benchmark generated successfully!"; default: return "Processing..."; } }; // Get the current step information for display const getCurrentStepInfo = () => { const totalSteps = BENCHMARK_STEPS.length; const currentStepIndex = activeStep; // If there's no active step yet if (currentStepIndex <= 1 && completedSteps.length === 0) { return `Starting (1/${totalSteps})`; } // If all steps are completed if (currentStepIndex >= totalSteps) { return `Complete (${totalSteps}/${totalSteps})`; } // Get current step name const currentStepName = STEP_LABELS[BENCHMARK_STEPS[currentStepIndex]] || "Processing"; return `${currentStepName} (${currentStepIndex}/${totalSteps})`; }; // Format elapsed time in HH:MM:SS const formatElapsedTime = () => { const hours = Math.floor(elapsedTime / 3600); const minutes = Math.floor((elapsedTime % 3600) / 60); const seconds = elapsedTime % 60; return [ hours.toString().padStart(2, "0"), minutes.toString().padStart(2, "0"), seconds.toString().padStart(2, "0"), ].join(":"); }; // If complete, stop the timer useEffect(() => { if (generationComplete && timerIntervalRef.current) { clearInterval(timerIntervalRef.current); } }, [generationComplete]); return ( {error ? ( {error} ) : ( <> {getPhaseTitle()} {/* Step progress indicator */} {getCurrentStepInfo()} {/* Timer display */} {formatElapsedTime()} )} {/* Use the LogDisplay component */} {/* */} ); }; export default BenchmarkGenerator;