Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
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: 4, | |
mt: 3, | |
mb: 3, | |
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; | |