|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Générateur de Quiz IA</title> |
|
<style> |
|
|
|
body { |
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; |
|
line-height: 1.6; |
|
padding: 20px; |
|
max-width: 800px; |
|
margin: 40px auto; |
|
background-color: #f4f7f9; |
|
color: #333; |
|
} |
|
.container { |
|
background-color: #fff; |
|
padding: 30px 40px; |
|
border-radius: 8px; |
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); |
|
} |
|
h1 { |
|
text-align: center; |
|
color: #2c3e50; |
|
margin-bottom: 35px; |
|
} |
|
|
|
.input-section { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 10px; |
|
margin-bottom: 25px; |
|
} |
|
.input-section label { |
|
flex-basis: 100%; |
|
margin-bottom: 8px; |
|
font-weight: bold; |
|
color: #555; |
|
} |
|
.input-section input[type="text"] { |
|
flex-grow: 1; |
|
padding: 12px 15px; |
|
border: 1px solid #ccc; |
|
border-radius: 4px; |
|
font-size: 1rem; |
|
box-sizing: border-box; |
|
} |
|
.input-section button { |
|
padding: 12px 20px; |
|
background-color: #3498db; |
|
color: white; |
|
border: none; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
font-size: 1rem; |
|
transition: background-color 0.3s ease; |
|
white-space: nowrap; |
|
} |
|
.input-section button:hover { |
|
background-color: #2980b9; |
|
} |
|
.input-section button:disabled { |
|
background-color: #bdc3c7; |
|
cursor: not-allowed; |
|
} |
|
|
|
|
|
#statusMessage { |
|
margin-top: 20px; |
|
margin-bottom: 20px; |
|
padding: 12px 15px; |
|
border-radius: 4px; |
|
text-align: center; |
|
font-weight: bold; |
|
display: none; |
|
border-left: 5px solid; |
|
} |
|
#statusMessage.loading { background-color: #eaf2f8; color: #3498db; border-left-color: #3498db;} |
|
#statusMessage.error { background-color: #fdedec; color: #c0392b; border-left-color: #c0392b;} |
|
#statusMessage.success { background-color: #e8f6f3; color: #1abc9c; border-left-color: #1abc9c;} |
|
#statusMessage.info { background-color: #fef9e7; color: #f39c12; border-left-color: #f39c12;} |
|
|
|
|
|
#quizContainer { |
|
margin-top: 30px; |
|
border-top: 1px solid #eee; |
|
padding-top: 25px; |
|
} |
|
.quiz-question { |
|
background-color: #ffffff; |
|
border: 1px solid #e0e0e0; |
|
border-radius: 6px; |
|
padding: 20px 25px; |
|
margin-bottom: 25px; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.05); |
|
} |
|
.quiz-question p.question-text { |
|
font-weight: 600; |
|
margin: 0 0 18px 0; |
|
color: #34495e; |
|
font-size: 1.1em; |
|
line-height: 1.4; |
|
} |
|
.options-list { |
|
list-style: none; |
|
padding: 0; |
|
margin: 0; |
|
} |
|
.option-item { |
|
margin-bottom: 12px; |
|
padding: 10px 12px; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
transition: background-color 0.2s ease, border-color 0.2s ease; |
|
border: 1px solid #eee; |
|
background-color: #f9fafb; |
|
} |
|
.option-item:hover:not(.disabled) { |
|
background-color: #f0f4f8; |
|
border-color: #dbe4f0; |
|
} |
|
.option-item input[type="radio"] { |
|
margin-right: 12px; |
|
vertical-align: middle; |
|
|
|
|
|
} |
|
.option-item label { |
|
font-weight: normal; |
|
display: inline; |
|
margin-bottom: 0; |
|
vertical-align: middle; |
|
color: #333; |
|
cursor: pointer; |
|
width: calc(100% - 30px); |
|
} |
|
|
|
|
|
.option-item.correct { |
|
background-color: #d4efdf; |
|
border-color: #2ecc71; |
|
font-weight: 500; |
|
color: #145a32; |
|
} |
|
.option-item.incorrect { |
|
background-color: #fdedec; |
|
border-color: #e74c3c; |
|
color: #943126; |
|
|
|
|
|
} |
|
|
|
.option-item.missed-correct { |
|
|
|
border: 2px solid #2ecc71; |
|
box-shadow: 0 0 0 2px rgba(46, 204, 113, 0.3); |
|
} |
|
|
|
.option-item.disabled { |
|
cursor: default; |
|
opacity: 0.9; |
|
} |
|
.option-item.disabled:hover { |
|
background-color: inherit; |
|
border-color: inherit; |
|
} |
|
|
|
|
|
|
|
.explanation { |
|
margin-top: 18px; |
|
padding: 12px 15px; |
|
background-color: #eaf2f8; |
|
border-left: 4px solid #3498db; |
|
font-size: 0.95em; |
|
color: #2c3e50; |
|
display: none; |
|
border-radius: 0 4px 4px 0; |
|
} |
|
.explanation strong { |
|
color: #2980b9; |
|
} |
|
.explanation.visible { |
|
display: block; |
|
} |
|
|
|
|
|
#submitQuizButton { |
|
display: block; |
|
margin: 35px auto 15px auto; |
|
padding: 12px 30px; |
|
background-color: #2ecc71; |
|
color: white; |
|
border: none; |
|
border-radius: 5px; |
|
cursor: pointer; |
|
font-size: 1.1rem; |
|
font-weight: 500; |
|
transition: background-color 0.3s ease, transform 0.1s ease; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
} |
|
#submitQuizButton:hover:not(:disabled) { |
|
background-color: #27ae60; |
|
transform: translateY(-1px); |
|
} |
|
#submitQuizButton:disabled { |
|
background-color: #95a5a6; |
|
cursor: not-allowed; |
|
box-shadow: none; |
|
transform: none; |
|
} |
|
|
|
#scoreContainer { |
|
text-align: center; |
|
font-size: 1.3em; |
|
font-weight: bold; |
|
margin-top: 25px; |
|
padding: 18px; |
|
background-color: #ecf0f1; |
|
border-radius: 5px; |
|
color: #2c3e50; |
|
border: 1px solid #dce4ec; |
|
display: none; |
|
} |
|
#scoreContainer .score-value { |
|
color: #3498db; |
|
font-size: 1.1em; |
|
} |
|
|
|
|
|
@media (max-width: 600px) { |
|
.container { |
|
padding: 20px; |
|
} |
|
.input-section { |
|
flex-direction: column; |
|
} |
|
.input-section input[type="text"], |
|
.input-section button { |
|
width: 100%; |
|
} |
|
h1 { |
|
font-size: 1.8em; |
|
} |
|
.quiz-question { |
|
padding: 15px; |
|
} |
|
#submitQuizButton { |
|
width: 90%; |
|
padding: 12px 20px; |
|
} |
|
#scoreContainer { |
|
font-size: 1.1em; |
|
} |
|
} |
|
|
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<h1>Générateur de Quiz IA</h1> |
|
|
|
<div class="input-section"> |
|
<label for="topicInput">Entrez un sujet pour le quiz :</label> |
|
<input type="text" id="topicInput" placeholder="Ex: La Révolution Française"> |
|
<button id="generateButton">Générer le Quiz</button> |
|
</div> |
|
|
|
<div id="statusMessage"></div> |
|
|
|
<div id="quizContainer"> |
|
|
|
</div> |
|
|
|
|
|
<button id="submitQuizButton" style="display: none;">Vérifier mes réponses</button> |
|
|
|
|
|
<div id="scoreContainer" style="display: none;"></div> |
|
|
|
</div> |
|
|
|
<script> |
|
|
|
const topicInput = document.getElementById('topicInput'); |
|
const generateButton = document.getElementById('generateButton'); |
|
const statusMessage = document.getElementById('statusMessage'); |
|
const quizContainer = document.getElementById('quizContainer'); |
|
const submitQuizButton = document.getElementById('submitQuizButton'); |
|
const scoreContainer = document.getElementById('scoreContainer'); |
|
|
|
|
|
let currentQuizData = []; |
|
|
|
|
|
generateButton.addEventListener('click', handleGenerateClick); |
|
|
|
topicInput.addEventListener('keypress', function(event) { |
|
if (event.key === 'Enter') { |
|
event.preventDefault(); |
|
handleGenerateClick(); |
|
} |
|
}); |
|
|
|
submitQuizButton.addEventListener('click', checkAnswers); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function handleGenerateClick() { |
|
const topic = topicInput.value.trim(); |
|
if (!topic) { |
|
displayMessage('Veuillez entrer un sujet.', 'error'); |
|
return; |
|
} |
|
|
|
|
|
generateButton.disabled = true; |
|
topicInput.disabled = true; |
|
quizContainer.innerHTML = ''; |
|
submitQuizButton.style.display = 'none'; |
|
submitQuizButton.disabled = true; |
|
submitQuizButton.textContent = 'Vérifier mes réponses'; |
|
scoreContainer.style.display = 'none'; |
|
displayMessage('Génération du quiz en cours... Cela peut prendre quelques instants.', 'loading'); |
|
|
|
try { |
|
|
|
const response = await fetch('/generate', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ topic: topic }), |
|
}); |
|
|
|
const data = await response.json(); |
|
clearMessage(); |
|
|
|
if (!response.ok) { |
|
|
|
const errorMsg = data?.error || `Erreur serveur ${response.status}.`; |
|
console.error("Erreur backend:", data); |
|
throw new Error(errorMsg); |
|
} |
|
|
|
|
|
if (data.success && data.quiz && Array.isArray(data.quiz)) { |
|
if (data.quiz.length > 0) { |
|
currentQuizData = data.quiz; |
|
displayQuiz(currentQuizData); |
|
displayMessage(`Quiz sur "${topic}" généré ! Répondez aux questions ci-dessous.`, 'success'); |
|
submitQuizButton.style.display = 'block'; |
|
submitQuizButton.disabled = false; |
|
} else { |
|
|
|
displayMessage("Aucune question n'a pu être générée pour ce sujet. Essayez d'être plus précis ou un sujet différent.", 'info'); |
|
} |
|
} else { |
|
|
|
throw new Error(data.error || 'Format de réponse inattendu du serveur.'); |
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('Erreur lors de la génération du quiz:', error); |
|
clearMessage(); |
|
|
|
displayMessage(`Erreur: ${error.message}. Veuillez réessayer ou contacter le support si le problème persiste.`, 'error'); |
|
} finally { |
|
|
|
generateButton.disabled = false; |
|
topicInput.disabled = false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function displayQuiz(quizData) { |
|
quizContainer.innerHTML = ''; |
|
|
|
quizData.forEach((q, index) => { |
|
|
|
if (!q || typeof q.question !== 'string' || !Array.isArray(q.options) || q.options.length < 2 || typeof q.correct_answer !== 'string' || !q.options.includes(q.correct_answer)) { |
|
console.warn("Format de question de quiz invalide ou incomplet ignoré:", q); |
|
|
|
|
|
return; |
|
} |
|
|
|
|
|
const questionElement = document.createElement('div'); |
|
questionElement.classList.add('quiz-question'); |
|
|
|
questionElement.dataset.questionIndex = index; |
|
|
|
|
|
const questionText = document.createElement('p'); |
|
questionText.classList.add('question-text'); |
|
|
|
|
|
questionText.innerHTML = `${index + 1}. ${q.question}`; |
|
questionElement.appendChild(questionText); |
|
|
|
|
|
const optionsList = document.createElement('div'); |
|
optionsList.classList.add('options-list'); |
|
|
|
|
|
const shuffledOptions = [...q.options].sort(() => Math.random() - 0.5); |
|
|
|
shuffledOptions.forEach((option, optionIndex) => { |
|
const optionId = `q${index}_option${optionIndex}`; |
|
|
|
|
|
const optionItem = document.createElement('div'); |
|
optionItem.classList.add('option-item'); |
|
|
|
const radioInput = document.createElement('input'); |
|
radioInput.type = 'radio'; |
|
radioInput.id = optionId; |
|
radioInput.name = `question_${index}`; |
|
radioInput.value = option; |
|
|
|
const radioLabel = document.createElement('label'); |
|
radioLabel.htmlFor = optionId; |
|
|
|
radioLabel.innerHTML = option; |
|
|
|
|
|
optionItem.addEventListener('click', () => { |
|
if (!radioInput.disabled) { |
|
radioInput.checked = true; |
|
} |
|
}); |
|
|
|
|
|
optionItem.appendChild(radioInput); |
|
optionItem.appendChild(radioLabel); |
|
optionsList.appendChild(optionItem); |
|
}); |
|
|
|
questionElement.appendChild(optionsList); |
|
|
|
|
|
|
|
if (q.explanation && typeof q.explanation === 'string' && q.explanation.trim() !== '') { |
|
const explanationElement = document.createElement('div'); |
|
explanationElement.classList.add('explanation'); |
|
|
|
explanationElement.innerHTML = `<strong>Explication :</strong> ${q.explanation}`; |
|
questionElement.appendChild(explanationElement); |
|
} |
|
|
|
|
|
quizContainer.appendChild(questionElement); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
function checkAnswers() { |
|
let score = 0; |
|
const totalQuestions = currentQuizData.length; |
|
|
|
if (totalQuestions === 0) return; |
|
|
|
|
|
submitQuizButton.disabled = true; |
|
submitQuizButton.textContent = 'Vérification...'; |
|
|
|
|
|
window.scrollTo({ top: quizContainer.offsetTop - 20, behavior: 'smooth' }); |
|
|
|
|
|
currentQuizData.forEach((qData, index) => { |
|
const questionElement = quizContainer.querySelector(`.quiz-question[data-question-index="${index}"]`); |
|
if (!questionElement) return; |
|
|
|
const optionsItems = questionElement.querySelectorAll('.option-item'); |
|
|
|
const selectedRadio = questionElement.querySelector(`input[name="question_${index}"]:checked`); |
|
const correctAnswerValue = qData.correct_answer; |
|
const explanationElement = questionElement.querySelector('.explanation'); |
|
|
|
let questionAnswered = false; |
|
|
|
optionsItems.forEach(item => { |
|
const radio = item.querySelector('input[type="radio"]'); |
|
item.classList.add('disabled'); |
|
radio.disabled = true; |
|
|
|
|
|
item.classList.remove('correct', 'incorrect', 'missed-correct'); |
|
|
|
|
|
if (radio.value === correctAnswerValue) { |
|
|
|
if (selectedRadio && selectedRadio.value === correctAnswerValue) { |
|
|
|
item.classList.add('correct'); |
|
score++; |
|
questionAnswered = true; |
|
} else { |
|
|
|
item.classList.add('missed-correct'); |
|
} |
|
} else if (selectedRadio && selectedRadio.value === radio.value) { |
|
|
|
item.classList.add('incorrect'); |
|
questionAnswered = true; |
|
} |
|
}); |
|
|
|
|
|
|
|
if (explanationElement) { |
|
explanationElement.classList.add('visible'); |
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
scoreContainer.innerHTML = `Votre score : <span class="score-value">${score} / ${totalQuestions}</span>`; |
|
scoreContainer.style.display = 'block'; |
|
|
|
|
|
submitQuizButton.textContent = 'Quiz Terminé !'; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function displayMessage(message, type = 'info') { |
|
statusMessage.textContent = message; |
|
|
|
statusMessage.className = type; |
|
statusMessage.style.display = 'block'; |
|
} |
|
|
|
|
|
|
|
|
|
function clearMessage() { |
|
statusMessage.textContent = ''; |
|
statusMessage.className = ''; |
|
statusMessage.style.display = 'none'; |
|
} |
|
|
|
</script> |
|
</body> |
|
</html> |