Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Quiz Maker</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .file-upload { | |
| border: 2px dashed #cbd5e0; | |
| transition: all 0.3s ease; | |
| } | |
| .file-upload:hover { | |
| border-color: #4f46e5; | |
| } | |
| .file-upload.dragover { | |
| background-color: #f0f9ff; | |
| border-color: #4f46e5; | |
| } | |
| .quiz-card { | |
| transition: transform 0.2s ease; | |
| } | |
| .quiz-card:hover { | |
| transform: translateY(-2px); | |
| } | |
| .progress-bar { | |
| transition: width 0.5s ease; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="text-center mb-12"> | |
| <h1 class="text-4xl font-bold text-indigo-700 mb-2">AI Quiz Maker</h1> | |
| <p class="text-gray-600 max-w-2xl mx-auto"> | |
| Upload your study materials and let our AI generate a personalized quiz to test your knowledge. | |
| </p> | |
| </header> | |
| <!-- Main Content --> | |
| <main> | |
| <!-- File Upload Section --> | |
| <section class="bg-white rounded-xl shadow-md p-6 mb-8"> | |
| <div class="flex flex-col md:flex-row gap-8"> | |
| <!-- Upload Area --> | |
| <div class="w-full md:w-1/2"> | |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Upload Your Files</h2> | |
| <div | |
| id="dropZone" | |
| class="file-upload rounded-lg p-8 text-center cursor-pointer mb-4" | |
| onclick="document.getElementById('fileInput').click()" | |
| > | |
| <div class="flex flex-col items-center justify-center"> | |
| <i class="fas fa-cloud-upload-alt text-5xl text-indigo-500 mb-4"></i> | |
| <p class="text-gray-600 mb-2 font-medium">Drag & drop files here or click to browse</p> | |
| <p class="text-gray-500 text-sm">Supported formats: PDF, DOCX, TXT</p> | |
| </div> | |
| <input | |
| type="file" | |
| id="fileInput" | |
| class="hidden" | |
| multiple | |
| accept=".pdf,.docx,.txt" | |
| onchange="handleFiles(this.files)" | |
| > | |
| </div> | |
| <div class="flex flex-wrap gap-2 mb-4" id="fileList"></div> | |
| </div> | |
| <!-- Settings --> | |
| <div class="w-full md:w-1/2"> | |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Quiz Settings</h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Quiz Type</label> | |
| <select id="quizType" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="multiple_choice">Multiple Choice</option> | |
| <option value="true_false">True/False</option> | |
| <option value="short_answer">Short Answer</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Number of Questions</label> | |
| <input | |
| type="range" | |
| id="questionCount" | |
| min="5" | |
| max="20" | |
| value="10" | |
| class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" | |
| > | |
| <div class="flex justify-between text-sm text-gray-600 mt-1"> | |
| <span>5</span> | |
| <span id="questionCountValue">10</span> | |
| <span>20</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">Difficulty</label> | |
| <select id="difficulty" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="easy">Easy</option> | |
| <option value="medium" selected>Medium</option> | |
| <option value="hard">Hard</option> | |
| </select> | |
| </div> | |
| <button | |
| id="generateBtn" | |
| class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200 flex items-center justify-center" | |
| onclick="generateQuiz()" | |
| > | |
| <i class="fas fa-magic mr-2"></i> Generate Quiz | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Processing Section (Hidden by default) --> | |
| <section id="processingSection" class="hidden bg-white rounded-xl shadow-md p-6 mb-8 text-center"> | |
| <div class="max-w-md mx-auto"> | |
| <div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-indigo-500 mx-auto mb-6"></div> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-2">Analyzing your files...</h3> | |
| <p class="text-gray-600 mb-4">Our AI is reading your documents and generating high-quality questions.</p> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div id="progressBar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <p id="progressText" class="text-sm text-gray-500 mt-2">0% complete</p> | |
| </div> | |
| </section> | |
| <!-- Quiz Results Section (Hidden by default) --> | |
| <section id="quizResults" class="hidden bg-white rounded-xl shadow-md p-6"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-2xl font-semibold text-gray-800">Your Generated Quiz</h2> | |
| <button | |
| id="downloadBtn" | |
| class="bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-lg transition duration-200 flex items-center" | |
| > | |
| <i class="fas fa-download mr-2"></i> Download Quiz | |
| </button> | |
| </div> | |
| <div id="quizContainer" class="space-y-6"></div> | |
| <div class="mt-8 pt-6 border-t border-gray-200"> | |
| <button | |
| id="submitQuizBtn" | |
| class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200" | |
| > | |
| Submit Quiz | |
| </button> | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| <script> | |
| // Global variables | |
| let uploadedFiles = []; | |
| let generatedQuiz = []; | |
| // DOM elements | |
| const dropZone = document.getElementById('dropZone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileList = document.getElementById('fileList'); | |
| const questionCount = document.getElementById('questionCount'); | |
| const questionCountValue = document.getElementById('questionCountValue'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const processingSection = document.getElementById('processingSection'); | |
| const quizResults = document.getElementById('quizResults'); | |
| const quizContainer = document.getElementById('quizContainer'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressText = document.getElementById('progressText'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const submitQuizBtn = document.getElementById('submitQuizBtn'); | |
| // Event listeners | |
| questionCount.addEventListener('input', () => { | |
| questionCountValue.textContent = questionCount.value; | |
| }); | |
| // Drag and drop functionality | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropZone.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropZone.addEventListener(eventName, highlight, false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropZone.addEventListener(eventName, unhighlight, false); | |
| }); | |
| function highlight() { | |
| dropZone.classList.add('dragover'); | |
| } | |
| function unhighlight() { | |
| dropZone.classList.remove('dragover'); | |
| } | |
| dropZone.addEventListener('drop', handleDrop, false); | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const files = dt.files; | |
| handleFiles(files); | |
| } | |
| // Handle file selection | |
| function handleFiles(files) { | |
| for (let i = 0; i < files.length; i++) { | |
| const file = files[i]; | |
| if (isValidFileType(file)) { | |
| if (!isFileAlreadyUploaded(file)) { | |
| uploadedFiles.push(file); | |
| displayFile(file); | |
| } | |
| } else { | |
| alert(`File type not supported: ${file.name}`); | |
| } | |
| } | |
| } | |
| function isValidFileType(file) { | |
| const validTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain']; | |
| return validTypes.includes(file.type); | |
| } | |
| function isFileAlreadyUploaded(file) { | |
| return uploadedFiles.some(uploadedFile => | |
| uploadedFile.name === file.name && | |
| uploadedFile.size === file.size | |
| ); | |
| } | |
| function displayFile(file) { | |
| const fileElement = document.createElement('div'); | |
| fileElement.className = 'flex items-center bg-gray-100 rounded-full px-4 py-2'; | |
| const iconClass = file.type === 'application/pdf' ? 'fa-file-pdf text-red-500' : | |
| file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ? 'fa-file-word text-blue-500' : | |
| 'fa-file-alt text-gray-500'; | |
| fileElement.innerHTML = ` | |
| <i class="fas ${iconClass} mr-2"></i> | |
| <span class="text-sm font-medium text-gray-700 mr-3 truncate max-w-xs">${file.name}</span> | |
| <button onclick="removeFile(${uploadedFiles.length - 1})" class="text-gray-400 hover:text-gray-600"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| `; | |
| fileList.appendChild(fileElement); | |
| } | |
| function removeFile(index) { | |
| uploadedFiles.splice(index, 1); | |
| fileList.innerHTML = ''; | |
| uploadedFiles.forEach((file, i) => displayFile(file)); | |
| } | |
| // Generate quiz function | |
| function generateQuiz() { | |
| if (uploadedFiles.length === 0) { | |
| alert('Please upload at least one file first.'); | |
| return; | |
| } | |
| // Show processing section | |
| processingSection.classList.remove('hidden'); | |
| generateBtn.disabled = true; | |
| // Simulate processing with progress bar | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += 5; | |
| progressBar.style.width = `${progress}%`; | |
| progressText.textContent = `${progress}% complete`; | |
| if (progress >= 100) { | |
| clearInterval(interval); | |
| processFilesAndGenerateQuiz(); | |
| } | |
| }, 200); | |
| } | |
| function processFilesAndGenerateQuiz() { | |
| // In a real app, this would send files to an AI API | |
| // For this demo, we'll simulate the AI processing | |
| // Read files (simulated) | |
| const quizType = document.getElementById('quizType').value; | |
| const questionCount = parseInt(document.getElementById('questionCount').value); | |
| const difficulty = document.getElementById('difficulty').value; | |
| // Generate sample quiz questions based on file names (simulated AI) | |
| generatedQuiz = []; | |
| for (let i = 0; i < questionCount; i++) { | |
| const fileIndex = i % uploadedFiles.length; | |
| const fileName = uploadedFiles[fileIndex].name; | |
| const fileType = uploadedFiles[fileIndex].type; | |
| let question = {}; | |
| if (quizType === 'multiple_choice') { | |
| question = generateMultipleChoiceQuestion(i, fileName, fileType, difficulty); | |
| } else if (quizType === 'true_false') { | |
| question = generateTrueFalseQuestion(i, fileName, fileType, difficulty); | |
| } else { | |
| question = generateShortAnswerQuestion(i, fileName, fileType, difficulty); | |
| } | |
| generatedQuiz.push(question); | |
| } | |
| // Hide processing section and show results | |
| processingSection.classList.add('hidden'); | |
| quizResults.classList.remove('hidden'); | |
| generateBtn.disabled = false; | |
| // Display the quiz | |
| displayQuiz(); | |
| } | |
| function generateMultipleChoiceQuestion(index, fileName, fileType, difficulty) { | |
| const questionTypes = [ | |
| `What is the main topic discussed in ${fileName}?`, | |
| `According to ${fileName}, which of the following is correct?`, | |
| `What would be the best summary of ${fileName}?`, | |
| `Which concept is most emphasized in ${fileName}?` | |
| ]; | |
| const question = questionTypes[index % questionTypes.length]; | |
| // Generate plausible options based on file info | |
| const options = [ | |
| getOptionBasedOnFile(fileName, fileType, 'correct'), | |
| getOptionBasedOnFile(fileName, fileType, 'plausible'), | |
| getOptionBasedOnFile(fileName, fileType, 'plausible'), | |
| getOptionBasedOnFile(fileName, fileType, 'random') | |
| ]; | |
| // Shuffle options | |
| for (let i = options.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [options[i], options[j]] = [options[j], options[i]]; | |
| } | |
| return { | |
| type: 'multiple_choice', | |
| question: question, | |
| options: options, | |
| correctAnswer: options.findIndex(opt => opt.isCorrect), | |
| userAnswer: null | |
| }; | |
| } | |
| function generateTrueFalseQuestion(index, fileName, fileType, difficulty) { | |
| const statements = [ | |
| `${fileName} primarily discusses this topic.`, | |
| `This concept is mentioned in ${fileName}.`, | |
| `The author of ${fileName} would agree with this statement.`, | |
| `This is a key finding from ${fileName}.` | |
| ]; | |
| const statement = statements[index % statements.length]; | |
| const isTrue = Math.random() > 0.5; | |
| return { | |
| type: 'true_false', | |
| question: statement, | |
| isTrue: isTrue, | |
| userAnswer: null | |
| }; | |
| } | |
| function generateShortAnswerQuestion(index, fileName, fileType, difficulty) { | |
| const questions = [ | |
| `What is the main purpose of ${fileName}?`, | |
| `Summarize the key points from ${fileName} in 2-3 sentences.`, | |
| `What problem does ${fileName} aim to solve?`, | |
| `How does ${fileName} contribute to its field?` | |
| ]; | |
| return { | |
| type: 'short_answer', | |
| question: questions[index % questions.length], | |
| sampleAnswer: getOptionBasedOnFile(fileName, fileType, 'correct').text, | |
| userAnswer: '' | |
| }; | |
| } | |
| function getOptionBasedOnFile(fileName, fileType, type) { | |
| const fileBase = fileName.split('.')[0]; | |
| if (type === 'correct') { | |
| return { | |
| text: `The ${fileBase} document discusses important concepts in this field`, | |
| isCorrect: true | |
| }; | |
| } else if (type === 'plausible') { | |
| const plausibleOptions = [ | |
| `The ${fileBase} focuses on historical aspects`, | |
| `This document presents experimental results`, | |
| `A theoretical framework is proposed in ${fileBase}`, | |
| `The ${fileBase} challenges existing paradigms` | |
| ]; | |
| return { | |
| text: plausibleOptions[Math.floor(Math.random() * plausibleOptions.length)], | |
| isCorrect: false | |
| }; | |
| } else { | |
| const randomOptions = [ | |
| 'None of the above', | |
| 'All of the above', | |
| 'The data is inconclusive', | |
| 'This was not addressed in the document' | |
| ]; | |
| return { | |
| text: randomOptions[Math.floor(Math.random() * randomOptions.length)], | |
| isCorrect: false | |
| }; | |
| } | |
| } | |
| function displayQuiz() { | |
| quizContainer.innerHTML = ''; | |
| generatedQuiz.forEach((question, index) => { | |
| const questionElement = document.createElement('div'); | |
| questionElement.className = 'quiz-card bg-gray-50 rounded-lg p-6 shadow-sm'; | |
| if (question.type === 'multiple_choice') { | |
| questionElement.innerHTML = ` | |
| <h3 class="text-lg font-medium text-gray-800 mb-4">${index + 1}. ${question.question}</h3> | |
| <div class="space-y-3"> | |
| ${question.options.map((option, i) => ` | |
| <div class="flex items-center"> | |
| <input | |
| type="radio" | |
| id="q${index}_opt${i}" | |
| name="q${index}" | |
| value="${i}" | |
| class="h-4 w-4 text-indigo-600 focus:ring-indigo-500" | |
| onchange="setUserAnswer(${index}, ${i})" | |
| > | |
| <label for="q${index}_opt${i}" class="ml-3 text-gray-700">${option.text}</label> | |
| </div> | |
| `).join('')} | |
| </div> | |
| `; | |
| } else if (question.type === 'true_false') { | |
| questionElement.innerHTML = ` | |
| <h3 class="text-lg font-medium text-gray-800 mb-4">${index + 1}. ${question.question}</h3> | |
| <div class="flex space-x-6"> | |
| <div class="flex items-center"> | |
| <input | |
| type="radio" | |
| id="q${index}_true" | |
| name="q${index}" | |
| value="true" | |
| class="h-4 w-4 text-indigo-600 focus:ring-indigo-500" | |
| onchange="setUserAnswer(${index}, true)" | |
| > | |
| <label for="q${index}_true" class="ml-3 text-gray-700">True</label> | |
| </div> | |
| <div class="flex items-center"> | |
| <input | |
| type="radio" | |
| id="q${index}_false" | |
| name="q${index}" | |
| value="false" | |
| class="h-4 w-4 text-indigo-600 focus:ring-indigo-500" | |
| onchange="setUserAnswer(${index}, false)" | |
| > | |
| <label for="q${index}_false" class="ml-3 text-gray-700">False</label> | |
| </div> | |
| </div> | |
| `; | |
| } else { | |
| questionElement.innerHTML = ` | |
| <h3 class="text-lg font-medium text-gray-800 mb-4">${index + 1}. ${question.question}</h3> | |
| <textarea | |
| id="q${index}_answer" | |
| class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" | |
| rows="3" | |
| placeholder="Type your answer here..." | |
| onchange="setUserAnswer(${index}, this.value)" | |
| ></textarea> | |
| `; | |
| } | |
| quizContainer.appendChild(questionElement); | |
| }); | |
| } | |
| function setUserAnswer(index, answer) { | |
| generatedQuiz[index].userAnswer = answer; | |
| } | |
| // Download quiz as JSON | |
| downloadBtn.addEventListener('click', () => { | |
| const quizData = { | |
| settings: { | |
| type: document.getElementById('quizType').value, | |
| questionCount: document.getElementById('questionCount').value, | |
| difficulty: document.getElementById('difficulty').value, | |
| files: uploadedFiles.map(file => file.name) | |
| }, | |
| questions: generatedQuiz | |
| }; | |
| const dataStr = JSON.stringify(quizData, null, 2); | |
| const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); | |
| const exportFileDefaultName = 'quiz.json'; | |
| const linkElement = document.createElement('a'); | |
| linkElement.setAttribute('href', dataUri); | |
| linkElement.setAttribute('download', exportFileDefaultName); | |
| linkElement.click(); | |
| }); | |
| // Submit quiz (simulated grading) | |
| submitQuizBtn.addEventListener('click', () => { | |
| let score = 0; | |
| let totalQuestions = generatedQuiz.length; | |
| generatedQuiz.forEach((question, index) => { | |
| if (question.type === 'multiple_choice') { | |
| if (question.userAnswer === question.correctAnswer) { | |
| score++; | |
| } | |
| } else if (question.type === 'true_false') { | |
| if (question.userAnswer === question.isTrue) { | |
| score++; | |
| } | |
| } | |
| // Short answer questions aren't auto-graded in this demo | |
| }); | |
| const percentage = Math.round((score / totalQuestions) * 100); | |
| // Show results | |
| quizContainer.innerHTML = ` | |
| <div class="bg-white rounded-xl shadow-md p-8 text-center"> | |
| <div class="inline-flex items-center justify-center w-24 h-24 bg-green-100 rounded-full mb-6"> | |
| <span class="text-3xl font-bold text-green-600">${percentage}%</span> | |
| </div> | |
| <h3 class="text-2xl font-semibold text-gray-800 mb-2">Quiz Completed!</h3> | |
| <p class="text-gray-600 mb-6"> | |
| You scored ${score} out of ${totalQuestions} questions. | |
| ${percentage >= 70 ? 'Great job!' : 'Keep practicing!'} | |
| </p> | |
| <button | |
| onclick="location.reload()" | |
| class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-6 rounded-lg transition duration-200" | |
| > | |
| Create New Quiz | |
| </button> | |
| </div> | |
| `; | |
| submitQuizBtn.classList.add('hidden'); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=UnknownUser0/quiz" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |