Spaces:
Build error
Build error
| 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 ( | |
| <Paper | |
| elevation={3} | |
| sx={{ | |
| p: 8, | |
| display: "flex", | |
| flexDirection: "column", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| minHeight: 200, | |
| position: "relative", | |
| }} | |
| > | |
| {/* Estimated time */} | |
| <Box | |
| sx={{ | |
| position: "absolute", | |
| top: 12, | |
| right: 12, | |
| backgroundColor: "rgba(0, 0, 0, 0.04)", | |
| borderRadius: "4px", | |
| px: 1, | |
| py: 0.5, | |
| display: "inline-flex", | |
| alignItems: "center", | |
| }} | |
| > | |
| <Typography | |
| variant="caption" | |
| sx={{ | |
| fontSize: "0.675rem", | |
| color: "text.secondary", | |
| fontWeight: 500, | |
| }} | |
| > | |
| Estimated time ~ 1m30s | |
| </Typography> | |
| </Box> | |
| {error ? ( | |
| <ErrorDisplay error={error} /> | |
| ) : ( | |
| <> | |
| <CircularProgress size={60} sx={{ mb: 2 }} /> | |
| <Typography variant="h6" component="div" gutterBottom> | |
| Creating benchmark... | |
| </Typography> | |
| {/* Step progress indicator */} | |
| <Typography variant="body1" color="text.secondary"> | |
| {getCurrentStepInfo()} | |
| </Typography> | |
| {/* Timer display */} | |
| <Box | |
| sx={{ | |
| display: "flex", | |
| alignItems: "center", | |
| mt: 1, | |
| color: "text.secondary", | |
| }} | |
| > | |
| <Typography variant="body2" sx={{ opacity: 0.5 }}> | |
| {formatElapsedTime()} | |
| </Typography> | |
| </Box> | |
| </> | |
| )} | |
| {/* Use the LogDisplay component */} | |
| {/* <LogDisplay logs={generationLogs} height={150} /> */} | |
| </Paper> | |
| ); | |
| }; | |
| export default Generator; | |