import streamlit as st import torch from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM import random import time # Configure page st.set_page_config( page_title="Text-to-Quiz Generator", page_icon="🧠", layout="wide" ) # Load the model with caching @st.cache_resource def load_model(): try: # Check if PyTorch is available print(f"PyTorch version: {torch.__version__}") print(f"CUDA available: {torch.cuda.is_available()}") # Using a smaller, more efficient model that works well for question generation model_name = "valhalla/t5-small-e2e-qg" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSeq2SeqLM.from_pretrained(model_name) # Set device device = "cuda" if torch.cuda.is_available() else "cpu" print(f"Using device: {device}") # Move model to device model = model.to(device) return model, tokenizer, device except Exception as e: st.error(f"Error loading model: {str(e)}") print(f"Error details: {str(e)}") return None, None, None # Custom CSS def load_css(): st.markdown(""" """, unsafe_allow_html=True) # Function to generate questions from a passage def generate_questions(model, tokenizer, device, text, num_questions=5): try: # Process text in chunks if it's too long max_length = 512 chunks = [] if len(text) > max_length: # Simple chunking based on sentences sentences = text.split('. ') current_chunk = "" for sentence in sentences: if len(current_chunk) + len(sentence) < max_length: current_chunk += sentence + ". " else: chunks.append(current_chunk) current_chunk = sentence + ". " if current_chunk: chunks.append(current_chunk) else: chunks = [text] all_generated_texts = [] # Process each chunk for chunk in chunks: inputs = tokenizer(chunk, return_tensors="pt", max_length=512, truncation=True) inputs = {k: v.to(device) for k, v in inputs.items()} # Generate with beam search for multiple diverse outputs with torch.no_grad(): outputs = model.generate( inputs["input_ids"], max_length=64, num_beams=5, num_return_sequences=min(3, num_questions), # Generate up to 3 questions per chunk temperature=1.0, diversity_penalty=1.0, num_beam_groups=5, early_stopping=True ) decoded_outputs = tokenizer.batch_decode(outputs, skip_special_tokens=True) all_generated_texts.extend(decoded_outputs) # If we have enough questions, stop if len(all_generated_texts) >= num_questions: break # Ensure we don't return more than num_questions all_generated_texts = all_generated_texts[:num_questions] # Process and extract questions and answers questions_answers = [] for generated_text in all_generated_texts: # Try to find question and answer if "?" in generated_text: parts = generated_text.split("?", 1) if len(parts) > 1: question = parts[0].strip() + "?" answer = parts[1].strip() # Clean up answer if it starts with common patterns for prefix in ["answer:", "a:", " - "]: if answer.lower().startswith(prefix): answer = answer[len(prefix):].strip() if question and answer and len(question) > 10: questions_answers.append({ "question": question, "answer": answer }) return questions_answers except Exception as e: st.error(f"Error generating questions: {str(e)}") print(f"Detailed error: {str(e)}") return [] # Function to create quiz from generated Q&A pairs def create_quiz(questions_answers, num_options=4): quiz_items = [] # First filter out very short answers and duplicates filtered_qa = [] seen_questions = set() for qa in questions_answers: q = qa["question"].strip() a = qa["answer"].strip() # Skip very short answers if len(a) < 2 or len(q) < 10: continue # Skip duplicate questions q_lower = q.lower() if q_lower in seen_questions: continue seen_questions.add(q_lower) filtered_qa.append({"question": q, "answer": a}) # Use the filtered Q&A pairs all_answers = [qa["answer"] for qa in filtered_qa] for i, qa in enumerate(filtered_qa): correct_answer = qa["answer"] # Create distractors by selecting random answers from other questions other_answers = [a for a in all_answers if a != correct_answer] if other_answers: # Select random distractors num_distractors = min(num_options - 1, len(other_answers)) distractors = random.sample(other_answers, num_distractors) # Combine correct answer and distractors options = [correct_answer] + distractors random.shuffle(options) quiz_items.append({ "id": i, "question": qa["question"], "correct_answer": correct_answer, "options": options }) return quiz_items # Alternative question generation using simpler approach def generate_questions_simple(text, num_questions=5): try: # Simple question generation for demonstration # In a real app, you'd use a proper NLP model # Extract sentences sentences = text.split('.') sentences = [s.strip() for s in sentences if len(s.strip()) > 20] # Select random sentences to turn into questions if len(sentences) < num_questions: selected_sentences = sentences else: selected_sentences = random.sample(sentences, num_questions) questions_answers = [] # Simple transformation of sentences into questions for sentence in selected_sentences: # Very simple question generation (not ideal but works as fallback) words = sentence.split() if len(words) < 5: continue # Extract key entities for answer potential_answer = " ".join(words[-3:]) # Create question from beginning of sentence question_words = words[:len(words)-3] question = " ".join(question_words) + "?" questions_answers.append({ "question": question, "answer": potential_answer }) return questions_answers except Exception as e: print(f"Error in simple question generation: {str(e)}") return [] # Main app def main(): load_css() # App title st.markdown('

🧠 Text-to-Quiz Generator

', unsafe_allow_html=True) col1, col2 = st.columns([2, 1]) with col1: st.markdown("### Enter a passage to generate quiz questions") passage = st.text_area( "Paste your text here:", height=200, placeholder="Enter a paragraph or article here to generate quiz questions..." ) with col2: st.markdown("### Settings") num_questions = st.slider("Number of questions to generate", 3, 10, 5) st.markdown("---") st.markdown(""" **Tips for best results:** - Use clear, factual content - Include specific details - Text length: 100-500 words works best - Educational content works better than narrative """) # Generate Quiz button with automatic rerun logic if "quiz_generated" not in st.session_state: st.session_state.quiz_generated = False if st.button("🧠 Generate Quiz"): if passage and len(passage) > 50: # Loading the model (with the cached resource) with st.spinner("Loading AI model..."): model, tokenizer, device = load_model() if model and tokenizer and device: # Generate questions with st.spinner("Generating questions..."): # Add a small delay for UX time.sleep(1) questions_answers = generate_questions(model, tokenizer, device, passage, num_questions) # If primary method fails, try fallback approach if not questions_answers: st.warning("Advanced question generation failed. Using simple approach instead.") questions_answers = generate_questions_simple(passage, num_questions) if questions_answers: # Create quiz quiz_items = create_quiz(questions_answers) if quiz_items: # Store in session state st.session_state.quiz_items = quiz_items st.session_state.user_answers = {} st.session_state.quiz_submitted = False st.session_state.show_explanations = False st.session_state.quiz_generated = True else: st.error("Couldn't create valid quiz questions. Please try a different text or add more content.") else: st.error("Failed to generate questions. Please try a different passage.") else: st.error("Failed to load the question generation model. Please try again.") else: st.warning("Please enter a longer passage (at least 50 characters).") # Display quiz if available in session state if "quiz_items" in st.session_state and st.session_state.quiz_items: st.markdown("---") st.markdown("## Your Quiz") quiz_items = st.session_state.quiz_items # Create a form for the quiz with st.form("quiz_form"): for i, item in enumerate(quiz_items): st.markdown(f'

Question {i+1}

{item["question"]}

', unsafe_allow_html=True) key = f"question_{item['id']}" st.session_state.user_answers[key] = st.radio( "Select your answer:", options=item["options"], key=key ) submit_button = st.form_submit_button("Submit Answers") if submit_button: st.session_state.quiz_submitted = True # Show results if quiz was submitted if st.session_state.quiz_submitted: score = 0 st.markdown("## Quiz Results") for i, item in enumerate(quiz_items): key = f"question_{item['id']}" user_answer = st.session_state.user_answers[key] correct = user_answer == item["correct_answer"] if correct: score += 1 st.markdown(f'

Question {i+1}: Correct! ✅

Your answer: {user_answer}

', unsafe_allow_html=True) else: st.markdown(f'

Question {i+1}: Incorrect ❌

Your answer: {user_answer}
Correct answer: {item["correct_answer"]}

', unsafe_allow_html=True) # Show score percentage = (score / len(quiz_items)) * 100 if percentage >= 80: color = "#28a745" # Green message = "Excellent! 🏆" elif percentage >= 60: color = "#17a2b8" # Blue message = "Good job! 👍" else: color = "#ffc107" # Yellow message = "Keep practicing! 📚" st.markdown(f'
{message}
Your Score: {score}/{len(quiz_items)} ({percentage:.1f}%)
', unsafe_allow_html=True) # Restart button if st.button("Generate Another Quiz"): # Clear session state for key in ["quiz_items", "user_answers", "quiz_submitted", "show_explanations", "quiz_generated"]: if key in st.session_state: del st.session_state[key] # No need for rerun as page will refresh naturally with next event if __name__ == "__main__": main()