|
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"; |
|
|
|
|
|
const SIMULATION_DURATION = 80000; |
|
|
|
|
|
const BENCHMARK_STEPS = [ |
|
"configuration", |
|
"provider_check", |
|
"ingestion", |
|
"upload_ingest_to_hub", |
|
"summarization", |
|
"chunking", |
|
"single_shot_question_generation", |
|
]; |
|
|
|
|
|
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", |
|
}; |
|
|
|
|
|
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", |
|
]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Generator = ({ sessionId, isDefaultDocument, onComplete }) => { |
|
const navigate = useNavigate(); |
|
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); |
|
|
|
|
|
const pollingIntervalRef = useRef(null); |
|
const timerIntervalRef = useRef(null); |
|
const startTimeRef = useRef(null); |
|
const simulationIntervalRef = useRef(null); |
|
const hasRedirectedRef = useRef(false); |
|
|
|
|
|
const resetGenerationStates = () => { |
|
setGenerating(true); |
|
setGenerationLogs([]); |
|
setError(null); |
|
setCurrentPhase("initializing"); |
|
setCompletedSteps([]); |
|
setActiveStep(1); |
|
}; |
|
|
|
|
|
const clearAllIntervals = () => { |
|
if (pollingIntervalRef.current) clearInterval(pollingIntervalRef.current); |
|
if (timerIntervalRef.current) clearInterval(timerIntervalRef.current); |
|
if (simulationIntervalRef.current) |
|
clearInterval(simulationIntervalRef.current); |
|
}; |
|
|
|
|
|
const notifyGenerationComplete = (success, logs, errorMsg = null) => { |
|
setGenerationComplete(true); |
|
clearAllIntervals(); |
|
|
|
if (onComplete) { |
|
onComplete({ |
|
success, |
|
sessionId, |
|
logs: logs || generationLogs, |
|
error: errorMsg, |
|
}); |
|
} |
|
}; |
|
|
|
|
|
useEffect(() => { |
|
|
|
startTimeRef.current = Date.now(); |
|
|
|
|
|
timerIntervalRef.current = setInterval(() => { |
|
const timeElapsed = Math.floor( |
|
(Date.now() - startTimeRef.current) / 1000 |
|
); |
|
setElapsedTime(timeElapsed); |
|
|
|
|
|
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); |
|
|
|
|
|
const handleVisibilityChange = () => { |
|
if ( |
|
document.visibilityState === "visible" && |
|
!isDefault && |
|
!generationComplete |
|
) { |
|
console.log("Page became visible, checking for missed steps..."); |
|
|
|
|
|
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(); |
|
} |
|
}; |
|
|
|
|
|
document.addEventListener("visibilitychange", handleVisibilityChange); |
|
|
|
|
|
if (isDefault) { |
|
simulateGeneration(); |
|
} else { |
|
generateBenchmark(); |
|
} |
|
|
|
|
|
return () => { |
|
clearAllIntervals(); |
|
document.removeEventListener("visibilitychange", handleVisibilityChange); |
|
}; |
|
}, [isDefault, sessionId, generationComplete, onComplete]); |
|
|
|
|
|
const simulateGeneration = () => { |
|
resetGenerationStates(); |
|
|
|
|
|
const totalSteps = SIMULATED_LOGS.length; |
|
const intervalPerStep = SIMULATION_DURATION / totalSteps; |
|
let currentStep = 0; |
|
|
|
|
|
const addNextLog = () => { |
|
if (currentStep < SIMULATED_LOGS.length) { |
|
const newLogs = [...generationLogs, SIMULATED_LOGS[currentStep]]; |
|
setGenerationLogs(newLogs); |
|
currentStep++; |
|
|
|
|
|
if (currentStep >= SIMULATED_LOGS.length) { |
|
|
|
setTimeout(() => { |
|
setCurrentPhase("complete"); |
|
notifyGenerationComplete(true, newLogs); |
|
}, 1000); |
|
} |
|
} |
|
}; |
|
|
|
|
|
simulationIntervalRef.current = setInterval(addNextLog, intervalPerStep); |
|
}; |
|
|
|
|
|
useEffect(() => { |
|
if (generationLogs.length === 0) return; |
|
|
|
|
|
const newCompletedSteps = []; |
|
|
|
|
|
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) { |
|
|
|
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; |
|
} |
|
|
|
|
|
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); |
|
} |
|
} |
|
}); |
|
|
|
|
|
let newActiveStep = activeStep; |
|
|
|
if (newCompletedSteps.length > 0) { |
|
|
|
const maxCompletedStepIndex = Math.max( |
|
...newCompletedSteps.map((step) => BENCHMARK_STEPS.indexOf(step)) |
|
); |
|
|
|
const calculatedStep = maxCompletedStepIndex + 1; |
|
|
|
|
|
if (calculatedStep > activeStep) { |
|
newActiveStep = calculatedStep; |
|
} |
|
|
|
|
|
if (newActiveStep >= BENCHMARK_STEPS.length) { |
|
newActiveStep = BENCHMARK_STEPS.length; |
|
} |
|
} else if (activeStep === 0) { |
|
|
|
newActiveStep = 1; |
|
} |
|
|
|
|
|
if (JSON.stringify(newCompletedSteps) !== JSON.stringify(completedSteps)) { |
|
setCompletedSteps(newCompletedSteps); |
|
} |
|
|
|
|
|
if (newActiveStep !== activeStep) { |
|
setActiveStep(newActiveStep); |
|
} |
|
|
|
|
|
if (isDefault) return; |
|
|
|
|
|
const recentLogs = generationLogs.slice(-10); |
|
|
|
|
|
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, |
|
]); |
|
|
|
|
|
const generateBenchmark = async () => { |
|
if (!sessionId) { |
|
setError("Missing session ID"); |
|
return; |
|
} |
|
|
|
resetGenerationStates(); |
|
|
|
try { |
|
|
|
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 || []); |
|
|
|
|
|
pollingIntervalRef.current = setInterval(async () => { |
|
|
|
if (generationComplete) { |
|
clearInterval(pollingIntervalRef.current); |
|
return; |
|
} |
|
|
|
try { |
|
|
|
const logsResponse = await fetch( |
|
`${API_CONFIG.BASE_URL}/benchmark-progress/${sessionId}` |
|
); |
|
|
|
if (logsResponse.ok) { |
|
const logsResult = await logsResponse.json(); |
|
|
|
|
|
if ( |
|
logsResult.logs && |
|
logsResult.logs.length > generationLogs.length |
|
) { |
|
setGenerationLogs(logsResult.logs); |
|
} |
|
|
|
|
|
if (logsResult.is_completed) { |
|
setGenerationComplete(true); |
|
clearInterval(pollingIntervalRef.current); |
|
} |
|
} |
|
} catch (error) { |
|
console.log("Error polling for logs:", error); |
|
|
|
} |
|
}, 2000); |
|
} else { |
|
|
|
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); |
|
} |
|
}; |
|
|
|
|
|
const getCurrentStepInfo = () => { |
|
const totalSteps = BENCHMARK_STEPS.length; |
|
const currentStepIndex = activeStep; |
|
|
|
|
|
if (currentStepIndex <= 1 && completedSteps.length === 0) { |
|
return `Starting (1/${totalSteps})`; |
|
} |
|
|
|
|
|
if (currentStepIndex >= totalSteps) { |
|
return `Complete (${totalSteps}/${totalSteps})`; |
|
} |
|
|
|
|
|
const currentStepName = |
|
STEP_LABELS[BENCHMARK_STEPS[currentStepIndex]] || "Processing"; |
|
|
|
return `${currentStepName} (${currentStepIndex}/${totalSteps})`; |
|
}; |
|
|
|
|
|
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(":"); |
|
}; |
|
|
|
|
|
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; |
|
|
|
setTimeout(() => { |
|
navigate(`/benchmark-display?session=${sessionId}`); |
|
}, 500); |
|
} else if (result && !result.success) { |
|
|
|
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> |
|
</> |
|
)} |
|
|
|
{} |
|
{} |
|
</Paper> |
|
); |
|
}; |
|
|
|
export default Generator; |
|
|