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"; import ErrorDisplay from "../common/ErrorDisplay"; // Durée de simulation en millisecondes pour les documents précalculés const SIMULATION_DURATION = 80000; // 20 seconds // Définir toutes les étapes du benchmark en séquence const BENCHMARK_STEPS = [ "configuration", "provider_check", "ingestion", "upload_ingest_to_hub", "summarization", "chunking", "single_shot_question_generation", ]; // Étiquettes des étapes pour l'affichage (noms plus conviviaux) const STEP_LABELS = { configuration: "Configuration", provider_check: "Finding providers", ingestion: "Ingestion", upload_ingest_to_hub: "Upload to Hub", summarization: "Summarization", chunking: "Chunking", single_shot_question_generation: "Question generation", evaluation_provider_check: "Checking evaluation providers", evaluation: "Running evaluations", evaluation_saving_results: "Saving evaluation results", }; // Messages de log simulés pour les documents précalculés const SIMULATED_LOGS = [ "[INFO] Initializing benchmark generation...", "[INFO] Generating base configuration file...", "[SUCCESS] Stage completed: configuration", "[INFO] Finding available providers for models...", "[SUCCESS] Stage completed: provider_check", "[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", ]; /** * Composant pour gérer la génération de benchmark et afficher les logs * * @param {Object} props - Propriétés du composant * @param {string} props.sessionId - ID de session pour le fichier uploadé * @param {boolean} props.isDefaultDocument - S'il s'agit d'un document précalculé * @param {Function} props.onComplete - Fonction à appeler lorsque la génération est terminée * @returns {JSX.Element} Composant de génération de benchmark */ const Generator = ({ sessionId, isDefaultDocument, onComplete }) => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const isDefault = searchParams.get("isDefault") === "true" || isDefaultDocument; // États du composant 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); // Références pour les intervalles et timers const pollingIntervalRef = useRef(null); const timerIntervalRef = useRef(null); const startTimeRef = useRef(null); const simulationIntervalRef = useRef(null); const hasRedirectedRef = useRef(false); // Fonction pour réinitialiser les états de génération const resetGenerationStates = () => { setGenerating(true); setGenerationLogs([]); setError(null); setCurrentPhase("initializing"); setCompletedSteps([]); setActiveStep(1); }; // Fonction pour arrêter les intervalles const clearAllIntervals = () => { if (pollingIntervalRef.current) clearInterval(pollingIntervalRef.current); if (timerIntervalRef.current) clearInterval(timerIntervalRef.current); if (simulationIntervalRef.current) clearInterval(simulationIntervalRef.current); }; // Fonction pour notifier la fin de la génération const notifyGenerationComplete = (success, logs, errorMsg = null) => { setGenerationComplete(true); clearAllIntervals(); if (onComplete) { onComplete({ success, sessionId, logs: logs || generationLogs, error: errorMsg, }); } }; // Démarrer la génération au montage du composant useEffect(() => { // Configurer l'heure de départ startTimeRef.current = Date.now(); // Démarrer le timer timerIntervalRef.current = setInterval(() => { const timeElapsed = Math.floor( (Date.now() - startTimeRef.current) / 1000 ); setElapsedTime(timeElapsed); // Vérifier si le temps écoulé dépasse 5 minutes et que nous ne sommes pas en mode simulation if (timeElapsed > 300 && !isDefault && !generationComplete) { setError( "The benchmark generation is taking too long. The demo is currently under heavy load, please try again later." ); notifyGenerationComplete(false, null, "Timeout error"); } }, 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..."); // Forcer une nouvelle requête pour récupérer les logs const checkCurrentState = async () => { try { const progressResponse = await fetch( `${API_CONFIG.BASE_URL}/benchmark-progress/${sessionId}` ); if (progressResponse.ok) { const progressResult = await progressResponse.json(); if (progressResult.logs) { setGenerationLogs(progressResult.logs); } if (progressResult.is_completed) { notifyGenerationComplete(true, progressResult.logs); } } } catch (error) { console.error("Error checking for missed steps:", error); } }; checkCurrentState(); } }; // Ajouter l'écouteur pour le changement de visibilité document.addEventListener("visibilitychange", handleVisibilityChange); // Lancer la simulation ou la génération if (isDefault) { simulateGeneration(); } else { generateBenchmark(); } // Nettoyer les intervalles et écouteurs lors du démontage return () => { clearAllIntervals(); document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [isDefault, sessionId, generationComplete, onComplete]); // Simuler la génération de benchmark pour les documents précalculés const simulateGeneration = () => { resetGenerationStates(); // Variables de timing pour la simulation const totalSteps = SIMULATED_LOGS.length; const intervalPerStep = SIMULATION_DURATION / totalSteps; let currentStep = 0; // Fonction pour ajouter le prochain message de log const addNextLog = () => { if (currentStep < SIMULATED_LOGS.length) { const newLogs = [...generationLogs, SIMULATED_LOGS[currentStep]]; setGenerationLogs(newLogs); currentStep++; // Vérifier si terminé if (currentStep >= SIMULATED_LOGS.length) { // Simulation terminée setTimeout(() => { setCurrentPhase("complete"); notifyGenerationComplete(true, newLogs); }, 1000); } } }; // Démarrer la simulation simulationIntervalRef.current = setInterval(addNextLog, intervalPerStep); }; // Déterminer la phase actuelle et les étapes terminées en fonction des logs useEffect(() => { if (generationLogs.length === 0) return; // Recalculer les étapes terminées à chaque fois const newCompletedSteps = []; // Vérifier les erreurs de limitation de débit et de disponibilité du modèle const hasError = generationLogs.some( (log) => log.includes("RATE_LIMIT_EXCEEDED") || log.includes("heavy load") || log.includes("rate limit") || log.includes("Required models not available") || log.includes("Configuration failed") || log.includes("Error") || log.includes("ERROR") ); if (hasError) { // Vérifier d'abord le cas spécifique du document avec info insuffisante const insufficientInfoMessage = generationLogs.find((log) => log.includes( "Failed to generate benchmark: The document does not contain enough information" ) ); if (insufficientInfoMessage) { setError( "Your document doesn't contain enough information to generate a benchmark. Please try with a more comprehensive document that includes richer content." ); notifyGenerationComplete( false, null, "Insufficient document information" ); return; } const errorMessage = generationLogs.find( (log) => log.includes("Required models not available") || log.includes("Configuration failed") || log.includes("Error generating configuration") ) || "The demo is under heavy load at the moment. Please try again later."; setError(errorMessage); notifyGenerationComplete(false, null, errorMessage); return; } // Identifier toutes les étapes terminé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 en fonction des étapes terminé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; // Mettre à jour uniquement si la nouvelle étape est plus avancée que l'étape actuelle if (calculatedStep > activeStep) { newActiveStep = calculatedStep; } // S'assurer que 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 que 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 uniquement si elle a changé if (newActiveStep !== activeStep) { setActiveStep(newActiveStep); } // Ignorer le reste du traitement des logs si nous simulons if (isDefault) return; // Vérifier les derniers logs pour déterminer la phase actuelle const recentLogs = generationLogs.slice(-10); // Détecter les conditions d'achèvement 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"); notifyGenerationComplete(true, 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, ]); // Générer le benchmark const generateBenchmark = async () => { if (!sessionId) { setError("Missing session ID"); return; } resetGenerationStates(); try { // Appeler l'API pour générer le 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 nous avons 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); } } } catch (error) { console.log("Error polling for logs:", error); // Ne pas arrêter le polling en cas d'erreurs réseau } }, 2000); // Sondage toutes les 2 secondes } else { // Gérer l'erreur 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); } }; // Obtenir les informations sur l'étape actuelle pour l'affichage const getCurrentStepInfo = () => { const totalSteps = BENCHMARK_STEPS.length; const currentStepIndex = activeStep; // S'il n'y a pas encore d'étape active if (currentStepIndex <= 1 && completedSteps.length === 0) { return `Starting (1/${totalSteps})`; } // Si toutes les étapes sont terminées if (currentStepIndex >= totalSteps) { return `Complete (${totalSteps}/${totalSteps})`; } // Obtenir le nom de l'étape actuelle const currentStepName = STEP_LABELS[BENCHMARK_STEPS[currentStepIndex]] || "Processing"; return `${currentStepName} (${currentStepIndex}/${totalSteps})`; }; // Formater le temps écoulé en 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(":"); }; // Si terminé, arrêter le timer useEffect(() => { if (generationComplete && timerIntervalRef.current) { clearInterval(timerIntervalRef.current); } }, [generationComplete]); const handleGenerationComplete = (result) => { console.log("Benchmark generation completed:", result); if (result && result.success && !hasRedirectedRef.current) { hasRedirectedRef.current = true; // Marquer que la redirection a été faite // Légère pause avant de naviguer pour éviter les problèmes de synchronisation setTimeout(() => { navigate(`/benchmark-display?session=${sessionId}`); }, 500); } else if (result && !result.success) { // Afficher l'erreur au lieu de rediriger setError(result.error || "An error occurred during benchmark generation"); } }; return ( {/* Estimated time */} Estimated time ~ 1m30s {error ? ( ) : ( <> Creating benchmark... {/* Step progress indicator */} {getCurrentStepInfo()} {/* Timer display */} {formatElapsedTime()} )} {/* Use the LogDisplay component */} {/* */} ); }; export default Generator;