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"; // Simulation time in milliseconds for pre-calculated documents const SIMULATION_DURATION = 80000; // 20 seconds // Define all benchmark steps in sequence const BENCHMARK_STEPS = [ "configuration", "provider_check", "ingestion", "upload_ingest_to_hub", "summarization", "chunking", "single_shot_question_generation", ]; // Step labels for display (more user-friendly names) 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", }; // Simulated log messages for pre-calculated documents 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", ]; /** * 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(); // Reference for the timeout let timeoutRef = null; // Start timer timerIntervalRef.current = setInterval(() => { const timeElapsed = Math.floor( (Date.now() - startTimeRef.current) / 1000 ); setElapsedTime(timeElapsed); // Check if the elapsed time exceeds 8 minutes (480 seconds) and we are not in simulation mode if (timeElapsed > 480 && !isDefault && !generationComplete) { // Display an error message in case of timeout setError( "The benchmark generation is taking too long. The demo is currently under heavy load, please try again later." ); setGenerationComplete(true); // Clear intervals if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } if (timerIntervalRef.current) { clearInterval(timerIntervalRef.current); } } }, 1000); // Handler to detect when the page becomes visible again const handleVisibilityChange = () => { if ( document.visibilityState === "visible" && !isDefault && !generationComplete ) { console.log("Page became visible, checking for missed steps..."); // Force a new request to retrieve the logs const checkCurrentState = async () => { try { // First try to retrieve the benchmark logs 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); } // If the task is complete, update the state if (logsResult.is_completed) { setGenerationComplete(true); if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } if (onComplete) { onComplete({ success: true, sessionId, logs: logsResult.logs, }); } } } else { // If the benchmark task does not exist, try the configuration logs 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(); } }; // Add the listener for visibility change 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; // Recalculate completed steps completely each time // instead of just adding new steps const newCompletedSteps = []; // Check for rate limiting errors const hasRateLimitError = generationLogs.some( (log) => log.includes("RATE_LIMIT_EXCEEDED") || log.includes("heavy load") || log.includes("rate limit") ); if (hasRateLimitError) { setError( "The demo is under heavy load at the moment. Please try again later." ); setGenerationComplete(true); if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } return; } // Identify all completed steps in all 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); } } }); // Determine the active step based on completed steps let newActiveStep = activeStep; if (newCompletedSteps.length > 0) { // Find the most advanced step in the logs const maxCompletedStepIndex = Math.max( ...newCompletedSteps.map((step) => BENCHMARK_STEPS.indexOf(step)) ); // Move to the next step const calculatedStep = maxCompletedStepIndex + 1; // Update only if the new step is more advanced than the current step if (calculatedStep > activeStep) { newActiveStep = calculatedStep; } // Ensure that activeStep does not exceed the total number of steps if (newActiveStep >= BENCHMARK_STEPS.length) { newActiveStep = BENCHMARK_STEPS.length; } } else if (activeStep === 0) { // If no step is found and the active step is 0, move to 1 newActiveStep = 1; } // Update the state if the steps have changed if (JSON.stringify(newCompletedSteps) !== JSON.stringify(completedSteps)) { setCompletedSteps(newCompletedSteps); } // Update the active step only if it has changed 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 || []); // Set up polling to track progress pollingIntervalRef.current = setInterval(async () => { // Check if we have already completed if (generationComplete) { clearInterval(pollingIntervalRef.current); return; } try { // Call the API to get the latest logs const logsResponse = await fetch( `${API_CONFIG.BASE_URL}/benchmark-progress/${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 complete if (logsResult.is_completed) { setGenerationComplete(true); clearInterval(pollingIntervalRef.current); // Notification is now handled in the useEffect above } } } catch (error) { console.log("Error polling for logs:", error); // Do not stop polling in case of network errors } }, 2000); // Poll every 2 seconds } 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 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 ( {/* Estimated time */} Estimated time: ~ 1m30s {error ? ( {error} ) : ( <> Creating benchmark... {/* Step progress indicator */} {getCurrentStepInfo()} {/* Timer display */} {formatElapsedTime()} )} {/* Use the LogDisplay component */} {/* */} ); }; export default BenchmarkGenerator;