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"; | |
// 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 ( | |
<Paper | |
elevation={3} | |
sx={{ | |
p: 4, | |
mt: 3, | |
mb: 3, | |
display: "flex", | |
flexDirection: "column", | |
alignItems: "center", | |
justifyContent: "center", | |
minHeight: 200, | |
}} | |
> | |
{error ? ( | |
<Alert severity="error" sx={{ width: "100%" }}> | |
{error} | |
</Alert> | |
) : ( | |
<> | |
<CircularProgress size={60} sx={{ mb: 2 }} /> | |
<Typography variant="h6" component="div" gutterBottom> | |
{getPhaseTitle()} | |
</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 BenchmarkGenerator; | |