tfrere's picture
add url importer | improve yourbench error handling | refactor
c750639
raw
history blame
18.9 kB
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;