|
<!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 Flashcards et Quiz</title> |
|
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<style> |
|
|
|
.flashcard-transform { |
|
transition: transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out; |
|
} |
|
.flashcard-transform:hover { |
|
transform: translateY(-5px); |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-100 font-sans"> |
|
<div class="container mx-auto mt-8 px-4"> |
|
<h1 class="text-3xl font-bold text-center text-gray-800 mb-6">Générateur de Flashcards et Quiz</h1> |
|
|
|
|
|
<div class="max-w-2xl mx-auto mb-8"> |
|
<div class="bg-white rounded-lg shadow-md p-6"> |
|
<div class="mb-4"> |
|
<label for="topic" class="block text-sm font-medium text-gray-700 mb-1">Sujet</label> |
|
<input type="text" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" id="topic" placeholder="Entrez un sujet..."> |
|
</div> |
|
<div class="mb-4"> |
|
<label class="block text-sm font-medium text-gray-700 mb-2">Type de contenu</label> |
|
<div class="flex items-center space-x-4"> |
|
<div class="flex items-center"> |
|
<input id="typeFlashcards" name="contentType" type="radio" value="flashcards" checked class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"> |
|
<label for="typeFlashcards" class="ml-2 block text-sm text-gray-900"> |
|
Flashcards |
|
</label> |
|
</div> |
|
<div class="flex items-center"> |
|
<input id="typeQuiz" name="contentType" type="radio" value="quiz" class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"> |
|
<label for="typeQuiz" class="ml-2 block text-sm text-gray-900"> |
|
Quiz |
|
</label> |
|
</div> |
|
</div> |
|
</div> |
|
<button id="generateBtn" class="w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-150 ease-in-out"> |
|
Générer |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="loading" class="hidden text-center my-10 flex flex-col items-center justify-center"> |
|
<svg class="animate-spin h-8 w-8 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> |
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
|
</svg> |
|
<p class="mt-2 text-gray-600">Génération en cours... Cela peut prendre plusieurs minutes.</p> |
|
</div> |
|
|
|
|
|
<div id="flashcardsContainer" class="mt-6"> |
|
|
|
</div> |
|
|
|
|
|
<div id="quizContainer" class="mt-6 max-w-3xl mx-auto"> |
|
|
|
</div> |
|
</div> |
|
|
|
<script> |
|
const generateBtn = document.getElementById('generateBtn'); |
|
const loadingIndicator = document.getElementById('loading'); |
|
const flashcardsContainer = document.getElementById('flashcardsContainer'); |
|
const quizContainer = document.getElementById('quizContainer'); |
|
const topicInput = document.getElementById('topic'); |
|
|
|
generateBtn.addEventListener('click', function() { |
|
const topic = topicInput.value.trim(); |
|
if (!topic) { |
|
alert('Veuillez entrer un sujet.'); |
|
return; |
|
} |
|
|
|
const contentType = document.querySelector('input[name="contentType"]:checked').value; |
|
|
|
|
|
loadingIndicator.classList.remove('hidden'); |
|
flashcardsContainer.innerHTML = ''; |
|
quizContainer.innerHTML = ''; |
|
flashcardsContainer.classList.add('hidden'); |
|
quizContainer.classList.add('hidden'); |
|
|
|
|
|
fetch('/generate', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ topic, type: contentType }), |
|
}) |
|
.then(response => { |
|
if (!response.ok) { |
|
throw new Error(`Erreur HTTP: ${response.status}`); |
|
} |
|
return response.json(); |
|
}) |
|
.then(data => { |
|
loadingIndicator.classList.add('hidden'); |
|
|
|
if (data.error) { |
|
alert('Erreur: ' + data.error); |
|
return; |
|
} |
|
|
|
if (contentType === 'flashcards' && data.flashcards) { |
|
flashcardsContainer.classList.remove('hidden'); |
|
displayFlashcards(data.flashcards); |
|
} else if (contentType === 'quiz' && data.quiz) { |
|
quizContainer.classList.remove('hidden'); |
|
displayQuiz(data.quiz); |
|
} else { |
|
alert('Aucune donnée reçue ou format incorrect.'); |
|
} |
|
}) |
|
.catch(error => { |
|
loadingIndicator.classList.add('hidden'); |
|
console.error('Erreur lors de la génération:', error); |
|
alert('Erreur lors de la génération: ' + error.message + '. Vérifiez la console pour plus de détails.'); |
|
}); |
|
}); |
|
|
|
function displayFlashcards(flashcards) { |
|
flashcardsContainer.innerHTML = ` |
|
<div class="mb-4"> |
|
<h2 class="text-2xl font-semibold text-center text-gray-700 mb-1">Flashcards générées (${flashcards.length})</h2> |
|
<p class="text-center text-gray-500 text-sm">Cliquez sur une carte pour voir la réponse</p> |
|
</div> |
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> |
|
<!-- Flashcards ici --> |
|
</div> |
|
`; |
|
const gridContainer = flashcardsContainer.querySelector('.grid'); |
|
|
|
flashcards.forEach((card, index) => { |
|
const cardElement = document.createElement('div'); |
|
cardElement.className = 'bg-white rounded-lg shadow p-4 cursor-pointer flashcard-transform hover:shadow-lg'; |
|
cardElement.setAttribute('onclick', `toggleAnswer(${index})`); |
|
cardElement.innerHTML = ` |
|
<div class="font-semibold text-gray-800">${card.question}</div> |
|
<div id="answer-${index}" class="hidden mt-3 pt-3 border-t border-dashed border-gray-300 text-sm text-gray-700"> |
|
<strong class="font-bold text-gray-900">Réponse:</strong> ${card.answer} |
|
</div> |
|
`; |
|
gridContainer.appendChild(cardElement); |
|
}); |
|
} |
|
|
|
function toggleAnswer(index) { |
|
const answerElement = document.getElementById(`answer-${index}`); |
|
answerElement.classList.toggle('hidden'); |
|
} |
|
|
|
function displayQuiz(quizQuestions) { |
|
quizContainer.innerHTML = ` |
|
<div class="mb-4"> |
|
<h2 class="text-2xl font-semibold text-center text-gray-700 mb-1">Quiz généré (${quizQuestions.length} questions)</h2> |
|
<p class="text-center text-gray-500 text-sm">Sélectionnez une réponse pour chaque question</p> |
|
</div> |
|
`; |
|
|
|
quizQuestions.forEach((question, qIndex) => { |
|
const questionElement = document.createElement('div'); |
|
questionElement.className = 'bg-white rounded-lg shadow p-5 mb-5'; |
|
|
|
let optionsHtml = ''; |
|
const safeCorrectAnswer = question.correctAnswer.replace(/"/g, '"'); |
|
|
|
question.options.forEach((option, oIndex) => { |
|
optionsHtml += ` |
|
<div class="option block p-3 my-2 border border-gray-200 rounded-md cursor-pointer transition duration-150 ease-in-out hover:bg-gray-100 text-gray-800" |
|
id="q${qIndex}-o${oIndex}" |
|
data-correct="${safeCorrectAnswer}" |
|
onclick="selectOption(this, ${qIndex}, ${oIndex})"> |
|
${option} |
|
</div> |
|
`; |
|
}); |
|
|
|
questionElement.innerHTML = ` |
|
<h5 class="font-semibold text-lg mb-3 text-gray-800">${qIndex + 1}. ${question.question}</h5> |
|
<div class="options mt-3"> |
|
${optionsHtml} |
|
</div> |
|
<div class="explanation hidden mt-4 p-3 rounded-md bg-blue-50 border border-blue-200 text-sm text-blue-700" id="explanation-${qIndex}"> |
|
<strong class="font-bold">Explication:</strong> ${question.explanation} |
|
</div> |
|
`; |
|
quizContainer.appendChild(questionElement); |
|
}); |
|
} |
|
|
|
function selectOption(element, questionIndex, optionIndex) { |
|
const correctAnswer = element.dataset.correct; |
|
const questionOptions = quizContainer.querySelectorAll(`#q${questionIndex}-o0`)[0].parentElement.querySelectorAll('.option'); |
|
const explanationElement = document.getElementById(`explanation-${questionIndex}`); |
|
|
|
|
|
let alreadyAnswered = false; |
|
questionOptions.forEach(opt => { |
|
if (opt.classList.contains('bg-green-100') || opt.classList.contains('bg-red-100')) { |
|
alreadyAnswered = true; |
|
} |
|
}); |
|
if (alreadyAnswered) return; |
|
|
|
|
|
questionOptions.forEach(option => { |
|
option.classList.remove('bg-indigo-100', 'border-indigo-300', 'bg-green-100', 'border-green-300', 'text-green-800', 'bg-red-100', 'border-red-300', 'text-red-800'); |
|
option.classList.add('hover:bg-gray-100'); |
|
}); |
|
|
|
|
|
element.classList.add('bg-indigo-100', 'border-indigo-300'); |
|
element.classList.remove('hover:bg-gray-100'); |
|
|
|
const selectedText = element.textContent.trim(); |
|
if (selectedText === correctAnswer) { |
|
element.classList.remove('bg-indigo-100', 'border-indigo-300'); |
|
element.classList.add('bg-green-100', 'border-green-300', 'text-green-800'); |
|
} else { |
|
element.classList.remove('bg-indigo-100', 'border-indigo-300'); |
|
element.classList.add('bg-red-100', 'border-red-300', 'text-red-800'); |
|
|
|
|
|
questionOptions.forEach(option => { |
|
if (option.textContent.trim() === correctAnswer) { |
|
option.classList.add('bg-green-100', 'border-green-300', 'text-green-800'); |
|
option.classList.remove('hover:bg-gray-100'); |
|
} |
|
}); |
|
} |
|
|
|
|
|
questionOptions.forEach(option => { |
|
option.onclick = null; |
|
option.classList.remove('cursor-pointer', 'hover:bg-gray-100'); |
|
option.style.cursor = 'default'; |
|
}); |
|
|
|
|
|
explanationElement.classList.remove('hidden'); |
|
} |
|
</script> |
|
</body> |
|
</html> |
|
|