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"; // Define all benchmark steps in sequence const BENCHMARK_STEPS = [ "ingestion", "upload_ingest_to_hub", "summarization", "chunking", "single_shot_question_generation", "multi_hop_question_generation", "lighteval", ]; // Step labels for display (more user-friendly names) const STEP_LABELS = { ingestion: "Ingestion", upload_ingest_to_hub: "Upload to Hub", summarization: "Summarization", chunking: "Chunking", single_shot_question_generation: "Single-shot QG", multi_hop_question_generation: "Multi-hop QG", lighteval: "LightEval", }; /** * 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 {Function} props.onComplete - Function to call when generation is complete * @returns {JSX.Element} Benchmark generator component */ const BenchmarkGenerator = ({ sessionId, onComplete }) => { 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(0); 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); // 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); 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); } }; }, []); // Determine the current phase and completed steps based on logs useEffect(() => { if (generationLogs.length === 0) return; // Check all logs for completed stages const newCompletedSteps = [...completedSteps]; let newActiveStep = activeStep; 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); // Set active step to the index of the next step const stepIndex = BENCHMARK_STEPS.indexOf(completedStep); if (stepIndex >= 0 && stepIndex + 1 > newActiveStep) { newActiveStep = stepIndex + 1; if (newActiveStep >= BENCHMARK_STEPS.length) { newActiveStep = BENCHMARK_STEPS.length; } } } } }); // Update state if there are new completed steps if (newCompletedSteps.length > completedSteps.length) { setCompletedSteps(newCompletedSteps); setActiveStep(newActiveStep); } // 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] Ingestion process completed successfully") ) || recentLogs.some((log) => log.includes( "[SUCCESS] Configuration and ingestion completed successfully" ) ) || completedSteps.includes("lighteval") || newCompletedSteps.includes("lighteval"); 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 benchmark creation")) ) { setCurrentPhase("benchmarking"); } else if ( recentLogs.some((log) => log.includes("Generating base configuration")) ) { setCurrentPhase("configuring"); } }, [generationLogs, completedSteps, activeStep, sessionId, onComplete]); const generateBenchmark = async () => { if (!sessionId) { setError("Missing session ID"); return; } setGenerating(true); setGenerationLogs([]); setError(null); setCurrentPhase("initializing"); setCompletedSteps([]); setActiveStep(0); try { // Call the API to generate the benchmark const response = await fetch("http://localhost:3001/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 || []); // D'abord, on commence par interroger les logs de configuration const pollConfigLogs = async () => { try { // Call the API to get the config logs const configLogsResponse = await fetch( `http://localhost:3001/config-logs/${sessionId}` ); if (configLogsResponse.ok) { const configLogsResult = await configLogsResponse.json(); // Update logs if there are new ones if ( configLogsResult.logs && configLogsResult.logs.length > generationLogs.length ) { setGenerationLogs(configLogsResult.logs); } // If config task is completed, switch to polling benchmark logs if (configLogsResult.is_completed) { // Attendre un court instant pour permettre au serveur de démarrer le benchmark setTimeout(() => { console.log( "Configuration completed, switching to benchmark polling" ); clearInterval(configPollingIntervalRef.current); pollBenchmarkLogs(); }, 1000); } } } catch (error) { console.log("Error polling for config logs:", error); // Don't stop polling on network errors } }; // Fonction pour interroger les logs du benchmark const pollBenchmarkLogs = async () => { // Set up polling for benchmark logs pollingIntervalRef.current = setInterval(async () => { // Check if we already completed if (generationComplete) { clearInterval(pollingIntervalRef.current); return; } try { // Call the API to get the latest benchmark logs const logsResponse = await fetch( `http://localhost:3001/benchmark-logs/${sessionId}` ); if (logsResponse.ok) { const logsResult = await logsResponse.json(); // Update logs if there are new ones if ( logsResult.logs && logsResult.logs.length > generationLogs.length ) { setGenerationLogs(logsResult.logs); } // Check if the task is completed if (logsResult.is_completed) { setGenerationComplete(true); clearInterval(pollingIntervalRef.current); // Notification is now handled in the useEffect above } } } catch (error) { console.log("Error polling for benchmark logs:", error); // Don't stop polling on network errors } }, 3000); // Poll every 3 seconds }; // Démarrer le polling des logs de configuration const configPollingIntervalRef = { current: null }; configPollingIntervalRef.current = setInterval(pollConfigLogs, 1000); // Poll config logs more frequently (every second) } 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 "Generating configuration file..."; 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 === 0 && completedSteps.length === 0) { return `Starting... (0%)`; } // If all steps are completed if (currentStepIndex >= totalSteps) { return `Complete (100%)`; } // Calculate percentage const percentage = Math.round((currentStepIndex / totalSteps) * 100); // Get current step name const currentStepName = STEP_LABELS[BENCHMARK_STEPS[currentStepIndex]] || "Processing"; return `${currentStepName} (${percentage}%)`; }; // 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;