louiecerv commited on
Commit
0807180
·
1 Parent(s): a9c2070

updated the interface

Browse files
app.py → Exam_Maker.py RENAMED
File without changes
pages/1_About.py CHANGED
@@ -1,9 +1,42 @@
1
  import streamlit as st
2
 
3
  def show_about():
4
- st.title("About")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  # Add your About page content here
6
- st.write("This is the About page.")
7
 
8
  if st.session_state["authenticated"]:
9
  show_about()
 
1
  import streamlit as st
2
 
3
  def show_about():
4
+ about = """
5
+ ### WVSU Exam Maker: A Faculty Guide
6
+
7
+ The WVSU Exam Maker is a cutting-edge tool designed to assist faculty members of West Visayas State University in creating comprehensive exams with ease. Leveraging the latest AI technology from Google Gemini 2, this innovative app helps teachers generate questions in various formats, including:
8
+
9
+
10
+ * **Multiple Choice**: Assess students' knowledge with objective, structured questions.
11
+ * **True or False**: Evaluate students' understanding with concise, binary questions.
12
+ * **Short Response**: Encourage students to provide brief, written answers.
13
+ * **Essay**: Foster critical thinking and in-depth writing with longer, more open-ended questions.
14
+
15
+
16
+ ## Key Features
17
+
18
+ ### Text Prompt Page
19
+ Define exam requirements with precision using various input options.
20
+
21
+
22
+ ### Multimodal Prompt Page
23
+ Upload reference documents (PDF or image) to generate questions, including:
24
+
25
+
26
+ * Lecture materials
27
+ * Tables of specifications
28
+ * Rubrics
29
+ * Other relevant inputs
30
+
31
+
32
+ ## Important Note
33
+ While the WVSU Exam Maker utilizes advanced AI technology, it is essential to review the output carefully, as AI can make mistakes. User supervision is necessary to ensure accuracy.
34
+
35
+ ## Development Team
36
+ The WVSU Exam Maker was developed by the AI Research Team of the Management Information System Office.
37
+ """
38
  # Add your About page content here
39
+ st.markdown(about)
40
 
41
  if st.session_state["authenticated"]:
42
  show_about()
pages/2_Text_prompt.py CHANGED
@@ -1,8 +1,34 @@
1
  import streamlit as st
2
  import sqlite3
3
- from datetime import datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  def get_system_instruction(username):
 
6
  conn = sqlite3.connect('users.db')
7
  c = conn.cursor()
8
  c.execute('SELECT instruction FROM system_instructions WHERE username=?', (username,))
@@ -13,27 +39,601 @@ def get_system_instruction(username):
13
  else:
14
  return "Default system instruction."
15
 
16
- def show_text_prompt():
17
- st.subheader("Text Prompt")
18
- username = st.session_state["username"]
19
- system_instruction = get_system_instruction(username)
20
- st.write("System Instruction:")
21
- st.write(system_instruction)
22
- prompt = st.text_input("Enter your prompt:")
23
- if st.button("Ask AI"):
24
- save_user_prompt(username, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Text Prompt")
25
- # Call AI model here
26
- st.success(f"Prompt {prompt} sent to AI model.")
27
-
28
  def save_user_prompt(username, prompt_time, prompt_type):
 
 
29
  conn = sqlite3.connect('users.db')
30
  c = conn.cursor()
31
  c.execute('INSERT INTO user_prompts(username, prompt_time, prompt_type) VALUES (?,?,?)', (username, prompt_time, prompt_type))
32
  conn.commit()
33
  conn.close()
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  if st.session_state["authenticated"]:
36
- show_text_prompt()
 
37
  else:
38
  if not st.session_state["is_starting"]:
39
- st.write("You are not authenticated. Please log in to access this page.")
 
1
  import streamlit as st
2
  import sqlite3
3
+ import time
4
+ import datetime
5
+ from PIL import Image
6
+ import google.generativeai as genai
7
+ import os
8
+ from reportlab.pdfgen import canvas
9
+ from reportlab.lib.pagesizes import A4, letter
10
+ from io import BytesIO
11
+ import tempfile
12
+ import json
13
+ import re
14
+ from reportlab.platypus import Paragraph, Frame, Spacer
15
+ from reportlab.lib.styles import getSampleStyleSheet
16
+ import shutil
17
+
18
+ MODEL_ID = "gemini-2.0-flash-exp"
19
+ api_key = os.getenv("GEMINI_API_KEY")
20
+ model_id = MODEL_ID
21
+ genai.configure(api_key=api_key)
22
+ enable_stream = False
23
+
24
+ if "model" not in st.session_state:
25
+ st.session_state.model = genai.GenerativeModel(MODEL_ID)
26
+
27
+ if "chat" not in st.session_state:
28
+ st.session_state.chat = st.session_state.model.start_chat()
29
 
30
  def get_system_instruction(username):
31
+ """ Retrieves the system instruction for the user from the database. """
32
  conn = sqlite3.connect('users.db')
33
  c = conn.cursor()
34
  c.execute('SELECT instruction FROM system_instructions WHERE username=?', (username,))
 
39
  else:
40
  return "Default system instruction."
41
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  def save_user_prompt(username, prompt_time, prompt_type):
43
+ """ Saves the user prompt to the database for monitoring purposes. """
44
+
45
  conn = sqlite3.connect('users.db')
46
  c = conn.cursor()
47
  c.execute('INSERT INTO user_prompts(username, prompt_time, prompt_type) VALUES (?,?,?)', (username, prompt_time, prompt_type))
48
  conn.commit()
49
  conn.close()
50
 
51
+ def merge_json_strings(json_str1, json_str2):
52
+ """
53
+ Merges two JSON strings into one, handling potential markdown tags.
54
+
55
+ Args:
56
+ json_str1: The first JSON string, potentially with markdown tags.
57
+ json_str2: The second JSON string, potentially with markdown tags.
58
+
59
+ Returns:
60
+ A cleaned JSON string representing the merged JSON objects.
61
+ """
62
+
63
+ # Clean the JSON strings by removing markdown tags
64
+ cleaned_json_str1 = _clean_markdown(json_str1)
65
+ cleaned_json_str2 = _clean_markdown(json_str2)
66
+
67
+ try:
68
+ # Parse the cleaned JSON strings into Python dictionaries
69
+ data1 = json.loads(cleaned_json_str1)
70
+ data2 = json.loads(cleaned_json_str2)
71
+
72
+ # Merge the dictionaries
73
+ merged_data = _merge_dicts(data1, data2)
74
+
75
+ # Convert the merged dictionary back into a JSON string
76
+ return json.dumps(merged_data, indent=2)
77
+ except json.JSONDecodeError as e:
78
+ return f"Error decoding JSON: {e}"
79
+
80
+
81
+ def _clean_markdown(text):
82
+ """
83
+ Removes markdown tags from a string if they exist.
84
+ Otherwise, returns the original string unchanged.
85
+
86
+ Args:
87
+ text: The input string.
88
+
89
+ Returns:
90
+ The string with markdown tags removed, or the original string
91
+ if no markdown tags were found.
92
+ """
93
+ try:
94
+ # Check if the string contains markdown
95
+ if re.match(r"^```json\s*", text) and re.search(r"\s*```$", text):
96
+ # Remove leading ```json
97
+ text = re.sub(r"^```json\s*", "", text)
98
+ # Remove trailing ```
99
+ text = re.sub(r"\s*```$", "", text)
100
+ return text
101
+ except Exception as e:
102
+ # Log the error
103
+ st.error(f"Error cleaning markdown: {e}")
104
+ return None
105
+
106
+ def _merge_dicts(data1, data2):
107
+ """
108
+ Recursively merges two data structures.
109
+
110
+ Handles merging of dictionaries and lists.
111
+ For dictionaries, if a key exists in both and both values are dictionaries
112
+ or lists, they are merged recursively. Otherwise, the value from data2 is used.
113
+ For lists, the lists are concatenated.
114
+
115
+ Args:
116
+ data1: The first data structure (dictionary or list).
117
+ data2: The second data structure (dictionary or list).
118
+
119
+ Returns:
120
+ The merged data structure.
121
+
122
+ Raises:
123
+ ValueError: If the data types are not supported for merging.
124
+ """
125
+ if isinstance(data1, dict) and isinstance(data2, dict):
126
+ for key, value in data2.items():
127
+ if key in data1 and isinstance(data1[key], (dict, list)) and isinstance(value, type(data1[key])):
128
+ _merge_dicts(data1[key], value)
129
+ else:
130
+ data1[key] = value
131
+ return data1
132
+ elif isinstance(data1, list) and isinstance(data2, list):
133
+ return data1 + data2
134
+ else:
135
+ raise ValueError("Unsupported data types for merging")
136
+
137
+ def create_json(metadata, content):
138
+ """
139
+ Creates a JSON string combining metadata and content.
140
+
141
+ Args:
142
+ metadata: A dictionary containing metadata information.
143
+ content: A dictionary containing the quiz content.
144
+
145
+ Returns:
146
+ A string representing the combined JSON data.
147
+ """
148
+
149
+ # Create metadata with timestamp
150
+ metadata = {
151
+ "subject": metadata.get("subject", ""),
152
+ "topic": metadata.get("topic", ""),
153
+ "num_questions": metadata.get("num_questions", 0),
154
+ "exam_type": metadata.get("exam_type", ""),
155
+ "timestamp": datetime.datetime.now().isoformat()
156
+ }
157
+
158
+ # Combine metadata and content
159
+ combined_data = {"metadata": metadata, "content": content}
160
+
161
+ # Convert to JSON string
162
+ json_string = json.dumps(combined_data, indent=4)
163
+
164
+ return json_string
165
+
166
+ def create_pdf(data):
167
+ """Creates a PDF file with text wrapping for quiz content."""
168
+ try:
169
+ # Load the JSON data
170
+ data = json.loads(data)
171
+
172
+ if 'metadata' not in data or 'content' not in data:
173
+ st.error("Error: Invalid data format. Missing 'metadata' or 'content' keys.")
174
+ return None
175
+
176
+ metadata = data['metadata']
177
+ content = data['content']
178
+
179
+ # Validate metadata
180
+ required_metadata_keys = ['subject', 'topic', 'exam_type', 'num_questions']
181
+ if not all(key in metadata for key in required_metadata_keys):
182
+ st.error("Error: Invalid metadata format. Missing required keys.")
183
+ return None
184
+
185
+ # Create a unique filename with timestamp
186
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
187
+ pdf_filename = f"quiz_output_{timestamp}.pdf"
188
+
189
+ # Get the temporary directory
190
+ temp_dir = tempfile.gettempdir()
191
+ pdf_path = os.path.join(temp_dir, pdf_filename)
192
+
193
+ c = canvas.Canvas(pdf_path, pagesize=A4)
194
+ c.setFont("Helvetica", 10)
195
+
196
+ exam_type = metadata['exam_type']
197
+
198
+ styles = getSampleStyleSheet()
199
+ style_normal = styles["Normal"]
200
+
201
+ y_position = 750
202
+ line_height = 15
203
+ frame_width = 500
204
+ first_page = True
205
+
206
+ for idx, q in enumerate(content):
207
+ if not isinstance(q, dict):
208
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
209
+ continue
210
+
211
+ if first_page:
212
+ # Print metadata once
213
+ for key, label in [("subject", "Subject"), ("topic", "Topic"),
214
+ ("exam_type", "Type"), ("num_questions", "Number of Questions")]:
215
+ c.drawString(50, y_position, f"{label}: {metadata[key]}")
216
+ y_position -= line_height
217
+
218
+ y_position -= line_height # Extra space before questions
219
+ first_page = False
220
+
221
+ # Print question number
222
+ question_text = f"{idx+1}. "
223
+ c.drawString(50, y_position, question_text)
224
+ x_position = 70 # Adjust starting position for question text
225
+
226
+ # --- Changes for better text flow ---
227
+ # Split the question into words
228
+ words = q.get('question', q.get('statement', '')).split()
229
+ current_line = ""
230
+ for word in words:
231
+ temp_line = current_line + " " + word
232
+ text_width = c.stringWidth(temp_line, "Helvetica", 10)
233
+ if text_width <= frame_width:
234
+ current_line = temp_line
235
+ else:
236
+ c.drawString(x_position, y_position, current_line)
237
+ y_position -= line_height
238
+ current_line = word
239
+ if y_position < 50:
240
+ c.showPage()
241
+ c.setFont("Helvetica", 10)
242
+ y_position = 750
243
+ # Print the last line of the question
244
+ c.drawString(x_position, y_position, current_line)
245
+ y_position -= line_height
246
+ # --- End of changes ---
247
+
248
+ if exam_type == "Multiple Choice":
249
+ # Validate question structure
250
+ required_question_keys = ['question', 'options', 'correct_answer']
251
+ if not all(key in q for key in required_question_keys):
252
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
253
+ continue
254
+
255
+ # Print options
256
+ for option_idx, option in enumerate(q['options'], ord('a')):
257
+ c.drawString(70, y_position, f"{chr(option_idx)}) {option}")
258
+ y_position -= line_height
259
+ if y_position < 50:
260
+ c.showPage()
261
+ c.setFont("Helvetica", 10)
262
+ y_position = 750
263
+
264
+ # Print correct answer
265
+ c.drawString(70, y_position, f"Correct Answer: {q['correct_answer']}")
266
+ y_position -= line_height * 2
267
+
268
+ elif exam_type == "True or False":
269
+ # Validate question structure
270
+ required_question_keys = ['statement', 'options', 'correct_answer']
271
+ if not all(key in q for key in required_question_keys):
272
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
273
+ continue
274
+
275
+ # Print options
276
+ for option in q['options']:
277
+ c.drawString(70, y_position, f"{option}")
278
+ y_position -= line_height
279
+ if y_position < 50:
280
+ c.showPage()
281
+ c.setFont("Helvetica", 10)
282
+ y_position = 750
283
+
284
+ # Print correct answer
285
+ c.drawString(70, y_position, f"Correct Answer: {q['correct_answer']}")
286
+ y_position -= line_height * 2
287
+
288
+ elif exam_type in ["Short Response", "Essay Type"]:
289
+ # Validate question structure
290
+ required_question_keys = ['question', 'correct_answer']
291
+ if not all(key in q for key in required_question_keys):
292
+ st.error(f"Error: Invalid question format at index {idx}. Skipping...")
293
+ continue
294
+
295
+ # Print correct answer
296
+ answer_text = f"Correct Answer: {q['correct_answer']}"
297
+
298
+ # --- Changes for better text flow ---
299
+ # Split the answer into words
300
+ words = answer_text.split()
301
+ current_line = ""
302
+ for word in words:
303
+ temp_line = current_line + " " + word
304
+ text_width = c.stringWidth(temp_line, "Helvetica", 10)
305
+ if text_width <= frame_width:
306
+ current_line = temp_line
307
+ else:
308
+ c.drawString(x_position, y_position, current_line)
309
+ y_position -= line_height
310
+ current_line = word
311
+ if y_position < 50:
312
+ c.showPage()
313
+ c.setFont("Helvetica", 10)
314
+ y_position = 750
315
+ # Print the last line of the answer
316
+ c.drawString(x_position, y_position, current_line)
317
+ y_position -= line_height * 2
318
+ # --- End of changes ---
319
+
320
+ if y_position < 50:
321
+ c.showPage()
322
+ c.setFont("Helvetica", 10)
323
+ y_position = 750
324
+
325
+ # Add the notice at the end
326
+ notice = "This exam was generated by the WVSU Exam Maker (c) 2025 West Visayas State University"
327
+ c.drawString(50, y_position, notice)
328
+
329
+ c.save()
330
+ return pdf_path
331
+
332
+ except Exception as e:
333
+ st.error(f"Error creating PDF: {e}")
334
+ return None
335
+
336
+ def generate_quiz_content(data):
337
+ """
338
+ Separates the metadata and content from a JSON string containing exam data.
339
+ Creates a markdown formatted text that contains the exam metadata and
340
+ enumerates the questions, options and answers nicely formatted for readability.
341
+
342
+ Args:
343
+ data: A JSON string containing the exam data.
344
+
345
+ Returns:
346
+ A markdown formatted string.
347
+ """
348
+ data = json.loads(data)
349
+ metadata = data["metadata"]
350
+ content = data["content"]
351
+ exam_type = metadata["exam_type"]
352
+ if exam_type == "Multiple Choice":
353
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
354
+
355
+ **Exam Type:** {metadata['exam_type']}
356
+ **Number of Questions:** {metadata['num_questions']}
357
+ **Timestamp:** {metadata['timestamp']}
358
+
359
+ ---
360
+
361
+ """
362
+ for i, q in enumerate(content):
363
+ md_text += f"""Question {i+1}:
364
+ {q['question']}
365
+
366
+ """
367
+ for j, option in enumerate(q['options'], ord('a')):
368
+ md_text += f"""{chr(j)}. {option}
369
+
370
+ """
371
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
372
+
373
+ ---
374
+
375
+ """
376
+ md_text += """This exam was generated by the WVSU Exam Maker
377
+ (c) 2025 West Visayas State University
378
+ """
379
+
380
+ elif exam_type == "True or False":
381
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
382
+
383
+ **Exam Type:** {metadata['exam_type']}
384
+ **Number of Questions:** {metadata['num_questions']}
385
+ **Timestamp:** {metadata['timestamp']}
386
+
387
+ ---
388
+
389
+ """
390
+
391
+ for i, q in enumerate(content):
392
+ md_text += f"""Statement {i+1}:
393
+
394
+ {q['statement']}
395
+
396
+ """
397
+ for j, option in enumerate(q['options'], ord('a')):
398
+ md_text += f"""{option}
399
+ """
400
+
401
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
402
+
403
+ ---
404
+ """
405
+ md_text += """This exam was generated by the WVSU Exam Maker
406
+ (c) 2025 West Visayas State University"""
407
+
408
+ elif exam_type == "Short Response" or exam_type == "Essay Type":
409
+ md_text = f"""# {metadata['subject']} - {metadata['topic']}
410
+
411
+ **Exam Type:** {metadata['exam_type']}
412
+ **Number of Questions:** {metadata['num_questions']}
413
+ **Timestamp:** {metadata['timestamp']}
414
+
415
+ ---
416
+
417
+ """
418
+
419
+ for i, q in enumerate(content):
420
+ md_text += f"""Question {i+1}:
421
+
422
+ {q['question']}
423
+
424
+ """
425
+ md_text += f"""**Correct Answer:** {q['correct_answer']}
426
+
427
+ ---
428
+ """
429
+ md_text += """This exam was generated by the WVSU Exam Maker
430
+ (c) 2025 West Visayas State University"""
431
+
432
+ return md_text
433
+
434
+ def generate_metadata(subject, topic, num_questions, exam_type):
435
+ """Generates quiz metadata as a dictionary combining num_questions,
436
+ exam_type, and timestamp.
437
+
438
+ Args:
439
+ num_questions: The number of questions in the exam (int).
440
+ exam_type: The type of exam (str).
441
+
442
+ Returns:
443
+ A dictionary containing the quiz metadata.
444
+ """
445
+
446
+ # Format the timestamp
447
+ timestamp = datetime.datetime.now()
448
+ formatted_timestamp = timestamp.strftime("%Y-%m-%d %H:%M:%S")
449
+
450
+ metadata = {
451
+ "subject": subject,
452
+ "topic": topic,
453
+ "num_questions": num_questions,
454
+ "exam_type": exam_type,
455
+ "timestamp": formatted_timestamp
456
+ }
457
+
458
+ return metadata
459
+
460
+ def generate_text(prompt):
461
+ """Generates text based on the prompt."""
462
+ try:
463
+
464
+ # Send a text prompt to Gemini API
465
+ chat = st.session_state.chat
466
+ response = chat.send_message(
467
+ [
468
+ prompt
469
+ ],
470
+ stream=enable_stream
471
+ )
472
+
473
+ return response.text
474
+
475
+ except Exception as e:
476
+ st.error(f"An error occurred while generating text: {e}")
477
+ return None
478
+
479
+ def show_text_prompt():
480
+ st.subheader("Text Prompt")
481
+
482
+ username = st.session_state["username"]
483
+ st.write(f"Welcome, {username}! This page allows you to generate questions based on user inputs.")
484
+
485
+ # Display username and logout button on every page
486
+ st.sidebar.write(f"Current user: {st.session_state['username']}")
487
+
488
+ # User inputs
489
+ # Course selection
490
+ course = st.selectbox("Select Course",
491
+ ["Computer Science",
492
+ "Mathematics",
493
+ "History",
494
+ "Physics"])
495
+
496
+ # Year level selection
497
+ year_level = st.selectbox("Select Year Level",
498
+ ["1st Year",
499
+ "2nd Year",
500
+ "3rd Year",
501
+ "4th Year"])
502
+
503
+ # Subject selection
504
+ subject = st.text_input("Enter Subject",
505
+ "e.g., Calculus, World History, Intro to Programming")
506
+
507
+ # Topic selection
508
+ topic = st.text_input("Enter Topic",
509
+ "e.g., Derivatives, French Revolution, Data Structures")
510
+
511
+ # Question type selection
512
+ question_type = st.selectbox("Select Question Type",
513
+ ["Multiple Choice",
514
+ "True or False",
515
+ "Short Response",
516
+ "Essay Type"])
517
+
518
+ difficulty = st.selectbox("Select Difficulty",["easy","average","hard"])
519
+
520
+ #number of questions to generate
521
+ if question_type != "Essay Type":
522
+ num_questions = st.selectbox("Number of Questions to Generate",
523
+ [10, 20, 30, 40, 50])
524
+ else:
525
+ num_questions = st.selectbox("Number of Questions to Generate",
526
+ [1, 2, 3, 4, 5])
527
+
528
+ # Combine user inputs into a prompt
529
+ prompt = f"""Refer to the uploaded document. Generate a {question_type} question for a {year_level} {course} student
530
+ in {subject} on the topic of {topic} with a {difficulty} difficulty level.
531
+ The questions should require higher order thinking skills.
532
+ """
533
+
534
+ if question_type == "Multiple Choice":
535
+ prompt += """Provide 4 choices. Provide the correct answer in the format 'Answer: A'.
536
+ Use the following JSON format for each question:
537
+ [{
538
+ "question": "Your question here?",
539
+ "options": ["Option A", "Option B", "Option C", "Option D"],
540
+ "correct_answer": "full text of the correct answer"
541
+ }, ... more questions]
542
+ Ensure that the response only contains the JSON array of questions and nothing else.
543
+ """
544
+ elif question_type == "True or False":
545
+ prompt += """Indicate whether the statement is true or false. Keep the statement brief and concise.
546
+ Use the following JSON format for each question:
547
+ [{
548
+ "statement": "Your statement here",
549
+ "options": ["True", "False"],
550
+ "correct_answer": True"
551
+ }, ... more questions]
552
+ Ensure that the response only contains the JSON array of questions and nothing else.
553
+ """
554
+ elif question_type == "Short Response":
555
+ prompt += """Create question that require a word or short phrase as answer. Use the following JSON format for each question:
556
+ [{
557
+ "question": "Your question here?",
558
+ "correct_answer": A word or phrase"
559
+ }, ... more questions]
560
+ Ensure that the response only contains the JSON array of questions and nothing else.
561
+ """
562
+ elif question_type == "Essay Type":
563
+ prompt += """Create questions that require a short essay between 300 to 500 words.
564
+ Provide a detailed answer. Use the following JSON format for each question:
565
+ [{
566
+ "question": "Your question here?",
567
+ "correct_answer": The essay answer goes here."
568
+ }, ... more questions]
569
+ Ensure that the response only contains the JSON array of questions and nothing else.
570
+ """
571
+
572
+ if not question_type == "Essay Type":
573
+ prompt += f"Generate 10 questions. Do not repeat questions you have already given in previous prompts. Exclude markdown tags in the response."
574
+ else:
575
+ prompt += f" Generate {num_questions} questions. Do not repeat questions you have already given in previous prompts. Exclude markdown tags in the response"
576
+
577
+ full_quiz = ""
578
+
579
+ # Send button
580
+ if st.button("Generate Questions"):
581
+
582
+ if question_type == "Essay Type":
583
+ #prompt once
584
+ with st.spinner('Generating questions...'):
585
+ full_quiz = _clean_markdown(generate_text(prompt))
586
+
587
+ else:
588
+ if num_questions == 10:
589
+
590
+ #prompt once
591
+ with st.spinner('Generating questions...'):
592
+ full_quiz = _clean_markdown(generate_text(prompt))
593
+ else:
594
+ #prompt multiple times
595
+ times = num_questions//10
596
+ for i in range(times):
597
+ with st.spinner('Generating questions...'):
598
+ response = generate_text(prompt)
599
+
600
+ if i==0:
601
+ full_quiz = _clean_markdown(response)
602
+ else:
603
+ full_quiz = merge_json_strings(full_quiz, response)
604
+
605
+ metadata = generate_metadata(subject, topic, num_questions, question_type)
606
+
607
+ try:
608
+ # Attempt to load the string as JSON to validate it
609
+ content = json.loads(full_quiz)
610
+ except json.JSONDecodeError:
611
+ st.error("Error: Invalid JSON string for quiz content.")
612
+ st.stop()
613
+
614
+ json_string = create_json(metadata, content)
615
+
616
+ quiz_markdown = generate_quiz_content(json_string)
617
+ st.markdown(quiz_markdown)
618
+
619
+ pdf_path = create_pdf(json_string)
620
+
621
+ if pdf_path:
622
+ """Click the button to download the generated PDF."""
623
+ try:
624
+ with open(pdf_path, "rb") as f:
625
+ st.download_button("Download PDF", f, file_name=os.path.basename(pdf_path))
626
+ except Exception as e:
627
+ st.error(f"Error handling file download: {e}")
628
+ else:
629
+ st.error("Failed to generate the PDF. Please try again.")
630
+
631
+ #record the prompt for monitoring
632
+ save_user_prompt(username, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Multimodal")
633
+
634
  if st.session_state["authenticated"]:
635
+ show_text_prompt()
636
+
637
  else:
638
  if not st.session_state["is_starting"]:
639
+ st.write("You are not authenticated. Please log in to access this page.")
pages/3_Multimodal.py CHANGED
@@ -6,25 +6,15 @@ from PIL import Image
6
  import google.generativeai as genai
7
  import os
8
  from reportlab.pdfgen import canvas
9
- from reportlab.lib.pagesizes import A4
10
  from io import BytesIO
11
  import tempfile
12
  import json
13
  import re
14
- from reportlab.lib.pagesizes import letter
15
- from reportlab.pdfgen import canvas
16
  from reportlab.platypus import Paragraph, Frame, Spacer
17
  from reportlab.lib.styles import getSampleStyleSheet
18
  import shutil
19
 
20
- import json
21
- import os
22
- import datetime
23
- from reportlab.pdfgen import canvas
24
- from reportlab.lib.pagesizes import letter, A4
25
- from reportlab.lib.styles import getSampleStyleSheet
26
- from reportlab.platypus import Paragraph
27
-
28
  MODEL_ID = "gemini-2.0-flash-exp"
29
  api_key = os.getenv("GEMINI_API_KEY")
30
  model_id = MODEL_ID
@@ -41,6 +31,7 @@ if "is_new_file" not in st.session_state:
41
  st.session_state.is_new_file = True
42
 
43
  def get_system_instruction(username):
 
44
  conn = sqlite3.connect('users.db')
45
  c = conn.cursor()
46
  c.execute('SELECT instruction FROM system_instructions WHERE username=?', (username,))
@@ -52,6 +43,8 @@ def get_system_instruction(username):
52
  return "Default system instruction."
53
 
54
  def save_user_prompt(username, prompt_time, prompt_type):
 
 
55
  conn = sqlite3.connect('users.db')
56
  c = conn.cursor()
57
  c.execute('INSERT INTO user_prompts(username, prompt_time, prompt_type) VALUES (?,?,?)', (username, prompt_time, prompt_type))
@@ -185,7 +178,6 @@ def create_pdf(data):
185
 
186
  metadata = data['metadata']
187
  content = data['content']
188
-
189
 
190
  # Validate metadata
191
  required_metadata_keys = ['subject', 'topic', 'exam_type', 'num_questions']
@@ -200,11 +192,11 @@ def create_pdf(data):
200
  # Get the temporary directory
201
  temp_dir = tempfile.gettempdir()
202
  pdf_path = os.path.join(temp_dir, pdf_filename)
203
-
204
- c = canvas.Canvas(pdf_path, pagesize=letter)
205
  c.setFont("Helvetica", 10)
206
-
207
- exam_type = metadata['exam_type']
208
 
209
  styles = getSampleStyleSheet()
210
  style_normal = styles["Normal"]
@@ -218,6 +210,44 @@ def create_pdf(data):
218
  if not isinstance(q, dict):
219
  st.error(f"Error: Invalid question format at index {idx}. Skipping...")
220
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  if exam_type == "Multiple Choice":
222
  # Validate question structure
223
  required_question_keys = ['question', 'options', 'correct_answer']
@@ -225,29 +255,10 @@ def create_pdf(data):
225
  st.error(f"Error: Invalid question format at index {idx}. Skipping...")
226
  continue
227
 
228
- if first_page:
229
- # Print metadata once
230
- for key, label in [("subject", "Subject"), ("topic", "Topic"),
231
- ("exam_type", "Type"), ("num_questions", "Number of Questions")]:
232
- c.drawString(50, y_position, f"{label}: {metadata[key]}")
233
- y_position -= line_height
234
-
235
- y_position -= line_height # Extra space before questions
236
- first_page = False
237
-
238
- # Print question
239
- question_text = f"{idx+1}. {q['question']}"
240
- paragraph = Paragraph(question_text, style_normal)
241
- paragraph.wrapOn(c, frame_width, 100)
242
- paragraph.drawOn(c, 50, y_position)
243
-
244
- y_position -= paragraph.height
245
-
246
  # Print options
247
  for option_idx, option in enumerate(q['options'], ord('a')):
248
  c.drawString(70, y_position, f"{chr(option_idx)}) {option}")
249
  y_position -= line_height
250
-
251
  if y_position < 50:
252
  c.showPage()
253
  c.setFont("Helvetica", 10)
@@ -264,29 +275,10 @@ def create_pdf(data):
264
  st.error(f"Error: Invalid question format at index {idx}. Skipping...")
265
  continue
266
 
267
- if first_page:
268
- # Print metadata once
269
- for key, label in [("subject", "Subject"), ("topic", "Topic"),
270
- ("exam_type", "Type"), ("num_questions", "Number of Questions")]:
271
- c.drawString(50, y_position, f"{label}: {metadata[key]}")
272
- y_position -= line_height
273
-
274
- y_position -= line_height # Extra space before questions
275
- first_page = False
276
-
277
- # Print question
278
- question_text = f"{idx+1}. {q['statement']}"
279
- paragraph = Paragraph(question_text, style_normal)
280
- paragraph.wrapOn(c, frame_width, 100)
281
- paragraph.drawOn(c, 50, y_position)
282
-
283
- y_position -= paragraph.height
284
-
285
  # Print options
286
- for option_idx, option in enumerate(q['options'], ord('a')):
287
  c.drawString(70, y_position, f"{option}")
288
  y_position -= line_height
289
-
290
  if y_position < 50:
291
  c.showPage()
292
  c.setFont("Helvetica", 10)
@@ -303,40 +295,40 @@ def create_pdf(data):
303
  st.error(f"Error: Invalid question format at index {idx}. Skipping...")
304
  continue
305
 
306
- if first_page:
307
- # Print metadata once
308
- for key, label in [("subject", "Subject"), ("topic", "Topic"),
309
- ("exam_type", "Type"), ("num_questions", "Number of Questions")]:
310
- c.drawString(50, y_position, f"{label}: {metadata[key]}")
 
 
 
 
 
 
 
 
 
311
  y_position -= line_height
 
 
 
 
 
 
 
 
 
312
 
313
- y_position -= line_height # Extra space before questions
314
- first_page = False
315
-
316
- # Print question
317
- question_text = f"{idx+1}. {q['question']}"
318
- paragraph = Paragraph(question_text, style_normal)
319
- paragraph.wrapOn(c, frame_width, y_position)
320
- paragraph_height = paragraph.height
321
- paragraph.drawOn(c, 50, y_position - paragraph_height)
322
- y_position -= paragraph_height + line_height # Adjust position after the paragraph
323
-
324
- # Print correct answer
325
- answer = f"Correct Answer: {q['correct_answer']}"
326
- paragraph = Paragraph(answer, style_normal)
327
- paragraph.wrapOn(c, frame_width, y_position)
328
- paragraph_height = paragraph.height
329
- paragraph.drawOn(c, 50, y_position - paragraph_height)
330
- y_position -= paragraph_height + line_height * 2 # Adjust position after the paragraph
331
 
332
  # Add the notice at the end
333
- if y_position < 50:
334
- c.showPage()
335
- y_position = 750
336
-
337
  notice = "This exam was generated by the WVSU Exam Maker (c) 2025 West Visayas State University"
338
  c.drawString(50, y_position, notice)
339
-
340
  c.save()
341
  return pdf_path
342
 
 
6
  import google.generativeai as genai
7
  import os
8
  from reportlab.pdfgen import canvas
9
+ from reportlab.lib.pagesizes import A4, letter
10
  from io import BytesIO
11
  import tempfile
12
  import json
13
  import re
 
 
14
  from reportlab.platypus import Paragraph, Frame, Spacer
15
  from reportlab.lib.styles import getSampleStyleSheet
16
  import shutil
17
 
 
 
 
 
 
 
 
 
18
  MODEL_ID = "gemini-2.0-flash-exp"
19
  api_key = os.getenv("GEMINI_API_KEY")
20
  model_id = MODEL_ID
 
31
  st.session_state.is_new_file = True
32
 
33
  def get_system_instruction(username):
34
+ """ Retrieves the system instruction for the user from the database. """
35
  conn = sqlite3.connect('users.db')
36
  c = conn.cursor()
37
  c.execute('SELECT instruction FROM system_instructions WHERE username=?', (username,))
 
43
  return "Default system instruction."
44
 
45
  def save_user_prompt(username, prompt_time, prompt_type):
46
+ """ Saves the user prompt to the database for monitoring purposes. """
47
+
48
  conn = sqlite3.connect('users.db')
49
  c = conn.cursor()
50
  c.execute('INSERT INTO user_prompts(username, prompt_time, prompt_type) VALUES (?,?,?)', (username, prompt_time, prompt_type))
 
178
 
179
  metadata = data['metadata']
180
  content = data['content']
 
181
 
182
  # Validate metadata
183
  required_metadata_keys = ['subject', 'topic', 'exam_type', 'num_questions']
 
192
  # Get the temporary directory
193
  temp_dir = tempfile.gettempdir()
194
  pdf_path = os.path.join(temp_dir, pdf_filename)
195
+
196
+ c = canvas.Canvas(pdf_path, pagesize=A4)
197
  c.setFont("Helvetica", 10)
198
+
199
+ exam_type = metadata['exam_type']
200
 
201
  styles = getSampleStyleSheet()
202
  style_normal = styles["Normal"]
 
210
  if not isinstance(q, dict):
211
  st.error(f"Error: Invalid question format at index {idx}. Skipping...")
212
  continue
213
+
214
+ if first_page:
215
+ # Print metadata once
216
+ for key, label in [("subject", "Subject"), ("topic", "Topic"),
217
+ ("exam_type", "Type"), ("num_questions", "Number of Questions")]:
218
+ c.drawString(50, y_position, f"{label}: {metadata[key]}")
219
+ y_position -= line_height
220
+
221
+ y_position -= line_height # Extra space before questions
222
+ first_page = False
223
+
224
+ # Print question number
225
+ question_text = f"{idx+1}. "
226
+ c.drawString(50, y_position, question_text)
227
+ x_position = 70 # Adjust starting position for question text
228
+
229
+ # --- Changes for better text flow ---
230
+ # Split the question into words
231
+ words = q.get('question', q.get('statement', '')).split()
232
+ current_line = ""
233
+ for word in words:
234
+ temp_line = current_line + " " + word
235
+ text_width = c.stringWidth(temp_line, "Helvetica", 10)
236
+ if text_width <= frame_width:
237
+ current_line = temp_line
238
+ else:
239
+ c.drawString(x_position, y_position, current_line)
240
+ y_position -= line_height
241
+ current_line = word
242
+ if y_position < 50:
243
+ c.showPage()
244
+ c.setFont("Helvetica", 10)
245
+ y_position = 750
246
+ # Print the last line of the question
247
+ c.drawString(x_position, y_position, current_line)
248
+ y_position -= line_height
249
+ # --- End of changes ---
250
+
251
  if exam_type == "Multiple Choice":
252
  # Validate question structure
253
  required_question_keys = ['question', 'options', 'correct_answer']
 
255
  st.error(f"Error: Invalid question format at index {idx}. Skipping...")
256
  continue
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  # Print options
259
  for option_idx, option in enumerate(q['options'], ord('a')):
260
  c.drawString(70, y_position, f"{chr(option_idx)}) {option}")
261
  y_position -= line_height
 
262
  if y_position < 50:
263
  c.showPage()
264
  c.setFont("Helvetica", 10)
 
275
  st.error(f"Error: Invalid question format at index {idx}. Skipping...")
276
  continue
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  # Print options
279
+ for option in q['options']:
280
  c.drawString(70, y_position, f"{option}")
281
  y_position -= line_height
 
282
  if y_position < 50:
283
  c.showPage()
284
  c.setFont("Helvetica", 10)
 
295
  st.error(f"Error: Invalid question format at index {idx}. Skipping...")
296
  continue
297
 
298
+ # Print correct answer
299
+ answer_text = f"Correct Answer: {q['correct_answer']}"
300
+
301
+ # --- Changes for better text flow ---
302
+ # Split the answer into words
303
+ words = answer_text.split()
304
+ current_line = ""
305
+ for word in words:
306
+ temp_line = current_line + " " + word
307
+ text_width = c.stringWidth(temp_line, "Helvetica", 10)
308
+ if text_width <= frame_width:
309
+ current_line = temp_line
310
+ else:
311
+ c.drawString(x_position, y_position, current_line)
312
  y_position -= line_height
313
+ current_line = word
314
+ if y_position < 50:
315
+ c.showPage()
316
+ c.setFont("Helvetica", 10)
317
+ y_position = 750
318
+ # Print the last line of the answer
319
+ c.drawString(x_position, y_position, current_line)
320
+ y_position -= line_height * 2
321
+ # --- End of changes ---
322
 
323
+ if y_position < 50:
324
+ c.showPage()
325
+ c.setFont("Helvetica", 10)
326
+ y_position = 750
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
  # Add the notice at the end
 
 
 
 
329
  notice = "This exam was generated by the WVSU Exam Maker (c) 2025 West Visayas State University"
330
  c.drawString(50, y_position, notice)
331
+
332
  c.save()
333
  return pdf_path
334
 
users.db CHANGED
Binary files a/users.db and b/users.db differ