Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -2,102 +2,195 @@ import gradio as gr
|
|
2 |
import pandas as pd
|
3 |
from mcq_generator import ImprovedMCQGenerator, is_suitable_for_students
|
4 |
import io
|
5 |
-
|
6 |
-
#
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
def generate_mcqs_ui(paragraph, num_questions):
|
10 |
-
if
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
12 |
|
|
|
13 |
if not is_suitable_for_students(paragraph):
|
14 |
-
|
|
|
|
|
|
|
15 |
|
16 |
try:
|
|
|
17 |
# Generate MCQs using the generator
|
18 |
mcqs = mcq_generator.generate_mcqs(paragraph, num_questions)
|
19 |
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
for idx, mcq in enumerate(mcqs):
|
23 |
options = ""
|
24 |
-
|
|
|
|
|
|
|
|
|
|
|
25 |
options += f"<b>{chr(65+opt_idx)}.</b> {option}<br>"
|
|
|
26 |
question_html = f"<div style='margin-bottom:20px; padding:10px; border:1px solid #ccc; border-radius:10px; background:#f9f9f9;'>"
|
27 |
-
question_html += f"<b>Q{idx+1}:</b> {
|
28 |
-
|
|
|
|
|
|
|
29 |
question_html += "</div>"
|
30 |
-
|
31 |
|
32 |
-
# Prepare
|
33 |
txt_output = ""
|
34 |
csv_data = []
|
35 |
|
36 |
for idx, mcq in enumerate(mcqs):
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
39 |
txt_output += f" {chr(65+opt_idx)}. {option}\n"
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
txt_buffer = io.StringIO()
|
53 |
txt_buffer.write(txt_output)
|
54 |
txt_buffer.seek(0)
|
55 |
|
56 |
-
# Create in-memory CSV file
|
57 |
csv_buffer = io.StringIO()
|
58 |
pd.DataFrame(csv_data).to_csv(csv_buffer, index=False)
|
59 |
csv_buffer.seek(0)
|
60 |
|
61 |
-
#
|
62 |
-
|
63 |
-
|
|
|
|
|
|
|
|
|
|
|
64 |
|
65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
|
67 |
except Exception as e:
|
68 |
-
|
|
|
|
|
|
|
69 |
|
70 |
|
71 |
-
# Define Gradio
|
72 |
-
with gr.Blocks() as demo:
|
73 |
gr.Markdown("<h1 style='text-align:center;'>📚 Smart MCQ Generator</h1>")
|
74 |
-
|
75 |
-
|
76 |
-
with gr.Row():
|
77 |
-
paragraph_input = gr.Textbox(lines=8, label="Enter Paragraph for MCQs", placeholder="Paste your study material here...")
|
78 |
-
with gr.Row():
|
79 |
-
num_questions_slider = gr.Slider(1, 10, step=1, value=5, label="Number of Questions")
|
80 |
-
|
81 |
-
# Generate Button
|
82 |
with gr.Row():
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
|
|
|
|
87 |
|
88 |
-
# MCQ output and download links
|
89 |
with gr.Row():
|
90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
|
92 |
with gr.Row():
|
93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
|
95 |
-
# Set up the button
|
96 |
generate_btn.click(
|
97 |
fn=generate_mcqs_ui,
|
98 |
inputs=[paragraph_input, num_questions_slider],
|
99 |
-
|
|
|
|
|
100 |
)
|
101 |
|
102 |
-
#
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import pandas as pd
|
3 |
from mcq_generator import ImprovedMCQGenerator, is_suitable_for_students
|
4 |
import io
|
5 |
+
import tempfile # <-- Import tempfile
|
6 |
+
import traceback # <-- Optional: for better error logging
|
7 |
+
|
8 |
+
# Initialize MCQ Generator (Keep this outside the function for efficiency)
|
9 |
+
try:
|
10 |
+
mcq_generator = ImprovedMCQGenerator()
|
11 |
+
print("✅ MCQ Generator Initialized Successfully")
|
12 |
+
except Exception as e:
|
13 |
+
print(f"❌ Failed to initialize MCQ Generator: {e}")
|
14 |
+
# You might want to stop the app or handle this more gracefully
|
15 |
+
mcq_generator = None
|
16 |
|
17 |
def generate_mcqs_ui(paragraph, num_questions):
|
18 |
+
# Check if generator initialized properly
|
19 |
+
if mcq_generator is None:
|
20 |
+
return None, None, None, "❌ Error: MCQ Generator failed to initialize. Check server logs."
|
21 |
+
|
22 |
+
# --- Input Validation ---
|
23 |
+
if not paragraph or not paragraph.strip():
|
24 |
+
return None, None, None, "⚠️ Please enter a valid paragraph."
|
25 |
|
26 |
+
print("\n--- Checking Suitability ---") # Add logging
|
27 |
if not is_suitable_for_students(paragraph):
|
28 |
+
print("❌ Paragraph deemed unsuitable.") # Add logging
|
29 |
+
# Return None for HTML and both file paths, plus the status message
|
30 |
+
return None, None, None, "❌ The paragraph is not suitable for MCQ generation (due to bias/toxicity/short length)."
|
31 |
+
print("✅ Paragraph suitable.") # Add logging
|
32 |
|
33 |
try:
|
34 |
+
print(f"--- Generating {num_questions} MCQs ---") # Add logging
|
35 |
# Generate MCQs using the generator
|
36 |
mcqs = mcq_generator.generate_mcqs(paragraph, num_questions)
|
37 |
|
38 |
+
if not mcqs:
|
39 |
+
print("⚠️ No MCQs generated (potentially issue in generator logic).") # Add logging
|
40 |
+
return None, None, None, "⚠️ Could not generate MCQs for this text. Try different content or fewer questions."
|
41 |
+
|
42 |
+
print(f"✅ Generated {len(mcqs)} MCQs successfully.") # Add logging
|
43 |
+
|
44 |
+
# --- Create Pretty HTML Output ---
|
45 |
+
pretty_mcqs_html = ""
|
46 |
for idx, mcq in enumerate(mcqs):
|
47 |
options = ""
|
48 |
+
# Ensure options exist and handle potential errors
|
49 |
+
mcq_options = mcq.get('options', [])
|
50 |
+
answer_index = mcq.get('answer_index', -1)
|
51 |
+
question_text = mcq.get('question', '[No Question Text]')
|
52 |
+
|
53 |
+
for opt_idx, option in enumerate(mcq_options):
|
54 |
options += f"<b>{chr(65+opt_idx)}.</b> {option}<br>"
|
55 |
+
|
56 |
question_html = f"<div style='margin-bottom:20px; padding:10px; border:1px solid #ccc; border-radius:10px; background:#f9f9f9;'>"
|
57 |
+
question_html += f"<b>Q{idx+1}:</b> {question_text}<br><br>{options}"
|
58 |
+
if 0 <= answer_index < len(mcq_options):
|
59 |
+
question_html += f"<i><b>Answer:</b> {chr(65+answer_index)}</i>"
|
60 |
+
else:
|
61 |
+
question_html += f"<i><b>Answer:</b> [Invalid Answer Index]</i>"
|
62 |
question_html += "</div>"
|
63 |
+
pretty_mcqs_html += question_html
|
64 |
|
65 |
+
# --- Prepare Text Output and CSV Data ---
|
66 |
txt_output = ""
|
67 |
csv_data = []
|
68 |
|
69 |
for idx, mcq in enumerate(mcqs):
|
70 |
+
mcq_options = mcq.get('options', [])
|
71 |
+
answer_index = mcq.get('answer_index', -1)
|
72 |
+
question_text = mcq.get('question', '[No Question Text]')
|
73 |
+
|
74 |
+
txt_output += f"Q{idx+1}: {question_text}\n"
|
75 |
+
for opt_idx, option in enumerate(mcq_options):
|
76 |
txt_output += f" {chr(65+opt_idx)}. {option}\n"
|
77 |
+
|
78 |
+
if 0 <= answer_index < len(mcq_options):
|
79 |
+
txt_output += f"Answer: {chr(65+answer_index)}\n\n"
|
80 |
+
else:
|
81 |
+
txt_output += f"Answer: [Invalid Answer Index]\n\n"
|
82 |
+
|
83 |
+
# Ensure 4 options for CSV, padding if necessary
|
84 |
+
options_padded = mcq_options + [''] * (4 - len(mcq_options))
|
85 |
+
csv_row = {
|
86 |
+
'Question': question_text,
|
87 |
+
'Option A': options_padded[0],
|
88 |
+
'Option B': options_padded[1],
|
89 |
+
'Option C': options_padded[2],
|
90 |
+
'Option D': options_padded[3],
|
91 |
+
'Answer': chr(65+answer_index) if 0 <= answer_index < len(mcq_options) else '[Invalid]'
|
92 |
+
}
|
93 |
+
csv_data.append(csv_row)
|
94 |
+
|
95 |
+
# --- Create In-Memory Buffers (Still useful for structuring data) ---
|
96 |
txt_buffer = io.StringIO()
|
97 |
txt_buffer.write(txt_output)
|
98 |
txt_buffer.seek(0)
|
99 |
|
|
|
100 |
csv_buffer = io.StringIO()
|
101 |
pd.DataFrame(csv_data).to_csv(csv_buffer, index=False)
|
102 |
csv_buffer.seek(0)
|
103 |
|
104 |
+
# --- Create Temporary Files and Get Paths ---
|
105 |
+
print("--- Creating temporary files ---") # Add logging
|
106 |
+
# Use delete=False so Gradio can access the file after the 'with' block closes it.
|
107 |
+
# Gradio should handle the cleanup of these temporary files.
|
108 |
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding='utf-8') as temp_txt:
|
109 |
+
temp_txt.write(txt_buffer.getvalue())
|
110 |
+
txt_filepath = temp_txt.name
|
111 |
+
print(f"Created TXT temp file: {txt_filepath}") # Add logging
|
112 |
|
113 |
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, encoding='utf-8') as temp_csv:
|
114 |
+
temp_csv.write(csv_buffer.getvalue())
|
115 |
+
csv_filepath = temp_csv.name
|
116 |
+
print(f"Created CSV temp file: {csv_filepath}") # Add logging
|
117 |
+
|
118 |
+
# --- Return HTML, File Paths, and Status ---
|
119 |
+
# Return order must match the `outputs` list in demo.launch()
|
120 |
+
return pretty_mcqs_html, txt_filepath, csv_filepath, "✅ MCQs generated successfully!"
|
121 |
|
122 |
except Exception as e:
|
123 |
+
print(f"❌ Error during MCQ generation process: {e}") # Add logging
|
124 |
+
print(traceback.format_exc()) # Print detailed traceback for debugging
|
125 |
+
# Return None for HTML and both file paths, plus the error message
|
126 |
+
return None, None, None, f"❌ Error generating MCQs: {str(e)}"
|
127 |
|
128 |
|
129 |
+
# --- Define Gradio Interface ---
|
130 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo: # Added a theme for aesthetics
|
131 |
gr.Markdown("<h1 style='text-align:center;'>📚 Smart MCQ Generator</h1>")
|
132 |
+
gr.Markdown("Enter a paragraph of study material, choose the number of questions, and get MCQs instantly!") # Added description
|
133 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
with gr.Row():
|
135 |
+
paragraph_input = gr.Textbox(
|
136 |
+
lines=10, # Increased lines
|
137 |
+
label="Enter Paragraph for MCQs",
|
138 |
+
placeholder="Paste your study material here (ideally 50-500 words)...",
|
139 |
+
elem_id="paragraph-input" # Added elem_id for potential CSS styling
|
140 |
+
)
|
141 |
|
|
|
142 |
with gr.Row():
|
143 |
+
num_questions_slider = gr.Slider(
|
144 |
+
minimum=1, # Explicit minimum
|
145 |
+
maximum=10, # Explicit maximum
|
146 |
+
step=1,
|
147 |
+
value=5,
|
148 |
+
label="Number of Questions to Generate",
|
149 |
+
elem_id="num-questions-slider"
|
150 |
+
)
|
151 |
|
152 |
with gr.Row():
|
153 |
+
generate_btn = gr.Button("🚀 Generate MCQs", variant="primary") # Made button primary
|
154 |
+
|
155 |
+
# Status box
|
156 |
+
status = gr.Textbox(label="Status", interactive=False, placeholder="Generation status will appear here...", elem_id="status-box")
|
157 |
+
|
158 |
+
# Use Accordion for tidier output section
|
159 |
+
with gr.Accordion("Generated MCQs & Downloads", open=True): # Default to open
|
160 |
+
# MCQ HTML output
|
161 |
+
mcq_output = gr.HTML(label="Generated MCQs")
|
162 |
+
|
163 |
+
# Download links - SEPARATED
|
164 |
+
with gr.Row():
|
165 |
+
download_txt = gr.File(label="Download MCQs (.txt)")
|
166 |
+
download_csv = gr.File(label="Download MCQs (.csv)")
|
167 |
|
168 |
+
# Set up the button click event
|
169 |
generate_btn.click(
|
170 |
fn=generate_mcqs_ui,
|
171 |
inputs=[paragraph_input, num_questions_slider],
|
172 |
+
# Outputs must match the return order of generate_mcqs_ui
|
173 |
+
outputs=[mcq_output, download_txt, download_csv, status],
|
174 |
+
api_name="generate_mcqs" # Added api_name for potential API usage
|
175 |
)
|
176 |
|
177 |
+
# Add Examples
|
178 |
+
gr.Examples(
|
179 |
+
examples=[
|
180 |
+
["The cell is the basic structural and functional unit of all living organisms. Cells can be classified into two main types: prokaryotic and eukaryotic. Prokaryotic cells, found in bacteria and archaea, lack a defined nucleus and membrane-bound organelles. In contrast, eukaryotic cells, which make up plants, animals, fungi, and protists, contain a nucleus that houses the cell’s DNA, as well as various organelles like mitochondria and the endoplasmic reticulum.", 3],
|
181 |
+
["The Industrial Revolution began in Great Britain in the late 18th century. It marked a shift from manual labor to machine-based manufacturing. Key innovations included the steam engine, improved by James Watt, and mechanized looms in the textile industry. This led to increased productivity, urbanization, and the rise of new social classes.", 4],
|
182 |
+
["Photosynthesis is the process used by plants, algae, and cyanobacteria to convert light energy into chemical energy, through a process that uses sunlight, water and carbon dioxide. This chemical energy is stored in carbohydrate molecules, such as sugars and starches, which are synthesized from carbon dioxide and water – hence the name photosynthesis, from the Greek φῶς, phos, 'light', and σύνθεσις, synthesis, 'putting together'. In most cases, oxygen is also released as a waste product.", 5]
|
183 |
+
],
|
184 |
+
inputs=[paragraph_input, num_questions_slider],
|
185 |
+
outputs=[mcq_output, download_txt, download_csv, status], # Ensure outputs match here too
|
186 |
+
fn=generate_mcqs_ui,
|
187 |
+
cache_examples=False # Maybe disable caching during debugging
|
188 |
+
)
|
189 |
+
|
190 |
+
|
191 |
+
# --- Launch the app ---
|
192 |
+
# share=True generates a public link (useful for HF Spaces)
|
193 |
+
# server_name="0.0.0.0" makes it accessible within the container network
|
194 |
+
# server_port=7860 is the standard Gradio port for Spaces
|
195 |
+
print("--- Launching Gradio App ---")
|
196 |
+
demo.launch(share=True, server_name="0.0.0.0", server_port=7860)
|