Spaces:
Sleeping
Sleeping
import gradio as gr | |
import pandas as pd | |
from mcq_generator import ImprovedMCQGenerator, is_suitable_for_students | |
import io | |
import tempfile # <-- Import tempfile | |
import traceback # <-- Optional: for better error logging | |
# Initialize MCQ Generator (Keep this outside the function for efficiency) | |
try: | |
mcq_generator = ImprovedMCQGenerator() | |
print("β MCQ Generator Initialized Successfully") | |
except Exception as e: | |
print(f"β Failed to initialize MCQ Generator: {e}") | |
# You might want to stop the app or handle this more gracefully | |
mcq_generator = None | |
def generate_mcqs_ui(paragraph, num_questions): | |
# Check if generator initialized properly | |
if mcq_generator is None: | |
return None, None, None, "β Error: MCQ Generator failed to initialize. Check server logs." | |
# --- Input Validation --- | |
if not paragraph or not paragraph.strip(): | |
return None, None, None, "β οΈ Please enter a valid paragraph." | |
print("\n--- Checking Suitability ---") # Add logging | |
if not is_suitable_for_students(paragraph): | |
print("β Paragraph deemed unsuitable.") # Add logging | |
# Return None for HTML and both file paths, plus the status message | |
return None, None, None, "β The paragraph is not suitable for MCQ generation (due to bias/toxicity/short length)." | |
print("β Paragraph suitable.") # Add logging | |
try: | |
print(f"--- Generating {num_questions} MCQs ---") # Add logging | |
# Generate MCQs using the generator | |
mcqs = mcq_generator.generate_mcqs(paragraph, num_questions) | |
if not mcqs: | |
print("β οΈ No MCQs generated (potentially issue in generator logic).") # Add logging | |
return None, None, None, "β οΈ Could not generate MCQs for this text. Try different content or fewer questions." | |
print(f"β Generated {len(mcqs)} MCQs successfully.") # Add logging | |
# --- Create Pretty HTML Output --- | |
pretty_mcqs_html = "" | |
for idx, mcq in enumerate(mcqs): | |
options = "" | |
# Ensure options exist and handle potential errors | |
mcq_options = mcq.get('options', []) | |
answer_index = mcq.get('answer_index', -1) | |
question_text = mcq.get('question', '[No Question Text]') | |
for opt_idx, option in enumerate(mcq_options): | |
options += f"<b>{chr(65+opt_idx)}.</b> {option}<br>" | |
question_html = f"<div style='margin-bottom:20px; padding:10px; border:1px solid #ccc; border-radius:10px; background:#f9f9f9;'>" | |
question_html += f"<b>Q{idx+1}:</b> {question_text}<br><br>{options}" | |
if 0 <= answer_index < len(mcq_options): | |
question_html += f"<i><b>Answer:</b> {chr(65+answer_index)}</i>" | |
else: | |
question_html += f"<i><b>Answer:</b> [Invalid Answer Index]</i>" | |
question_html += "</div>" | |
pretty_mcqs_html += question_html | |
# --- Prepare Text Output and CSV Data --- | |
txt_output = "" | |
csv_data = [] | |
for idx, mcq in enumerate(mcqs): | |
mcq_options = mcq.get('options', []) | |
answer_index = mcq.get('answer_index', -1) | |
question_text = mcq.get('question', '[No Question Text]') | |
txt_output += f"Q{idx+1}: {question_text}\n" | |
for opt_idx, option in enumerate(mcq_options): | |
txt_output += f" {chr(65+opt_idx)}. {option}\n" | |
if 0 <= answer_index < len(mcq_options): | |
txt_output += f"Answer: {chr(65+answer_index)}\n\n" | |
else: | |
txt_output += f"Answer: [Invalid Answer Index]\n\n" | |
# Ensure 4 options for CSV, padding if necessary | |
options_padded = mcq_options + [''] * (4 - len(mcq_options)) | |
csv_row = { | |
'Question': question_text, | |
'Option A': options_padded[0], | |
'Option B': options_padded[1], | |
'Option C': options_padded[2], | |
'Option D': options_padded[3], | |
'Answer': chr(65+answer_index) if 0 <= answer_index < len(mcq_options) else '[Invalid]' | |
} | |
csv_data.append(csv_row) | |
# --- Create In-Memory Buffers (Still useful for structuring data) --- | |
txt_buffer = io.StringIO() | |
txt_buffer.write(txt_output) | |
txt_buffer.seek(0) | |
csv_buffer = io.StringIO() | |
pd.DataFrame(csv_data).to_csv(csv_buffer, index=False) | |
csv_buffer.seek(0) | |
# --- Create Temporary Files and Get Paths --- | |
print("--- Creating temporary files ---") # Add logging | |
# Use delete=False so Gradio can access the file after the 'with' block closes it. | |
# Gradio should handle the cleanup of these temporary files. | |
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding='utf-8') as temp_txt: | |
temp_txt.write(txt_buffer.getvalue()) | |
txt_filepath = temp_txt.name | |
print(f"Created TXT temp file: {txt_filepath}") # Add logging | |
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, encoding='utf-8') as temp_csv: | |
temp_csv.write(csv_buffer.getvalue()) | |
csv_filepath = temp_csv.name | |
print(f"Created CSV temp file: {csv_filepath}") # Add logging | |
# --- Return HTML, File Paths, and Status --- | |
# Return order must match the `outputs` list in demo.launch() | |
return pretty_mcqs_html, txt_filepath, csv_filepath, "β MCQs generated successfully!" | |
except Exception as e: | |
print(f"β Error during MCQ generation process: {e}") # Add logging | |
print(traceback.format_exc()) # Print detailed traceback for debugging | |
# Return None for HTML and both file paths, plus the error message | |
return None, None, None, f"β Error generating MCQs: {str(e)}" | |
# --- Define Gradio Interface --- | |
with gr.Blocks(theme=gr.themes.Soft()) as demo: # Added a theme for aesthetics | |
gr.Markdown("<h1 style='text-align:center;'>π Smart MCQ Generator</h1>") | |
gr.Markdown("Enter a paragraph of study material, choose the number of questions, and get MCQs instantly!") # Added description | |
with gr.Row(): | |
paragraph_input = gr.Textbox( | |
lines=10, # Increased lines | |
label="Enter Paragraph for MCQs", | |
placeholder="Paste your study material here (ideally 50-500 words)...", | |
elem_id="paragraph-input" # Added elem_id for potential CSS styling | |
) | |
with gr.Row(): | |
num_questions_slider = gr.Slider( | |
minimum=1, # Explicit minimum | |
maximum=10, # Explicit maximum | |
step=1, | |
value=5, | |
label="Number of Questions to Generate", | |
elem_id="num-questions-slider" | |
) | |
with gr.Row(): | |
generate_btn = gr.Button("π Generate MCQs", variant="primary") # Made button primary | |
# Status box | |
status = gr.Textbox(label="Status", interactive=False, placeholder="Generation status will appear here...", elem_id="status-box") | |
# Use Accordion for tidier output section | |
with gr.Accordion("Generated MCQs & Downloads", open=True): # Default to open | |
# MCQ HTML output | |
mcq_output = gr.HTML(label="Generated MCQs") | |
# Download links - SEPARATED | |
with gr.Row(): | |
download_txt = gr.File(label="Download MCQs (.txt)") | |
download_csv = gr.File(label="Download MCQs (.csv)") | |
# Set up the button click event | |
generate_btn.click( | |
fn=generate_mcqs_ui, | |
inputs=[paragraph_input, num_questions_slider], | |
# Outputs must match the return order of generate_mcqs_ui | |
outputs=[mcq_output, download_txt, download_csv, status], | |
api_name="generate_mcqs" # Added api_name for potential API usage | |
) | |
# --- Launch the app --- | |
# share=True generates a public link (useful for HF Spaces) | |
# server_name="0.0.0.0" makes it accessible within the container network | |
# server_port=7860 is the standard Gradio port for Spaces | |
print("--- Launching Gradio App ---") | |
demo.launch(share=True, server_name="0.0.0.0", server_port=7860) |