File size: 15,792 Bytes
7461958
 
1830afe
7461958
 
 
a263c13
 
ad2cf05
 
738bef1
7461958
985cb05
1830afe
dc5787b
ad2cf05
7461958
1830afe
 
dc5787b
1830afe
 
dc5787b
1830afe
 
 
ad2cf05
 
 
 
 
 
1830afe
 
 
 
 
 
ad2cf05
1830afe
ad2cf05
 
 
 
738bef1
ad2cf05
 
 
 
 
 
 
 
 
 
 
 
56f04a7
 
ad2cf05
 
 
 
 
1830afe
ad2cf05
 
 
 
 
 
 
 
1830afe
 
 
 
 
 
 
 
 
 
 
 
ad2cf05
 
 
 
 
 
 
 
 
 
eccc273
ff40b9b
ad2cf05
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17b329c
ad2cf05
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a94602
ad2cf05
 
 
 
 
 
 
 
 
 
 
 
 
 
2428246
b948b56
ad2cf05
b948b56
 
8c624e7
b948b56
 
34b6ece
b948b56
ad2cf05
 
 
56f04a7
 
1661880
56f04a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad2cf05
 
 
 
 
 
 
56f04a7
 
7461958
ad2cf05
 
 
7461958
ad2cf05
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
import streamlit as st
import google.generativeai as genai
import openai
import os
import re  # For improved parsing

st.set_page_config(initial_sidebar_state="collapsed")

# --- Login Page ---
def login_page():
    # st.title("API Selection")

    
    api_options = ["Gemini API", "OpenAI API"]
    selected_api = st.selectbox("### Select API:", api_options)
    api_key = st.text_input(f"Enter your {selected_api} Key:", type="password")

    if selected_api == "Gemini API":
        model_options = ["gemini-pro"] # Add more Gemini models if available
        selected_model = st.selectbox("### Select Gemini Model:", model_options, index=0) # Default to first model
    elif selected_api == "OpenAI API":
        model_options = ["gpt-3.5-turbo", "gpt-4"] # Add more OpenAI models as needed
        selected_model = st.selectbox("### Select OpenAI Model:", model_options, index=0) # Default to first model
    else:
        selected_model = None # Should not happen, but for safety

    if st.button("Login"):
        if not api_key:
            st.error("Please enter your API key.")
        else:
            st.session_state.api_key = api_key
            st.session_state.selected_api = selected_api
            if selected_api == "Gemini API":
                st.session_state.gemini_model = selected_model
                st.session_state.openai_model = None # Clear OpenAI model if Gemini is selected
            elif selected_api == "OpenAI API":
                st.session_state.openai_model = selected_model
                st.session_state.gemini_model = None # Clear Gemini model if OpenAI is selected
            st.session_state.logged_in = True
            st.success(f"Logged in with {selected_api} using model: {selected_model}!")
            st.rerun() # Rerun to show the quiz app

# --- Quiz Application ---
def quiz_app():
    st.title("QuizLM 🧠")

    # Initialize session state for quiz data and progress (if not already initialized during login)
    if 'quiz_data' not in st.session_state:
        st.session_state.quiz_data = None
    if 'current_question_index' not in st.session_state:
        st.session_state.current_question_index = 0
    if 'user_answers' not in st.session_state:
        st.session_state.user_answers = []
    if 'quiz_completed' not in st.session_state:
        st.session_state.quiz_completed = False
    if 'score' not in st.session_state:
        st.session_state.score = 0
    if 'quiz_started' not in st.session_state: # Track if quiz generation has started
        st.session_state.quiz_started = False

    # API Configuration based on selection - Moved inside quiz_app after login
    if st.session_state.selected_api == "Gemini API":
        try:
            genai.configure(api_key=st.session_state.api_key)
            model = genai.GenerativeModel(st.session_state.gemini_model)
        except Exception as e:
            st.error(f"Error configuring Gemini API: {e}")
            st.error("Please check your API key and network connection.")
            st.session_state.logged_in = False # Force logout if API config fails
            del st.session_state.api_key
            del st.session_state.selected_api
            st.rerun() # Go back to login page
            return # Exit quiz_app
    elif st.session_state.selected_api == "OpenAI API":
        try:
            openai.api_key = st.session_state.api_key
            model_name = st.session_state.openai_model # Model name already selected in login
        except Exception as e:
            st.error(f"Error configuring OpenAI API: {e}")
            st.error("Please check your API key and network connection.")
            st.session_state.logged_in = False # Force logout if API config fails
            del st.session_state.api_key
            del st.session_state.selected_api
            st.rerun() # Go back to login page
            return # Exit quiz_app


    def parse_quiz_content(quiz_content):
        """Parses the quiz content and answer key into a structured format, handling title."""
        questions = []
        answer_key_dict = {}

        quiz_parts = quiz_content.split("Answer Key:")
        if len(quiz_parts) != 2:
            st.warning("Could not reliably separate quiz questions and answer key. Parsing might be imperfect.")
            return None, None

        question_section = quiz_parts[0].strip()
        answer_key_section = quiz_parts[1].strip()

        question_lines = question_section.strip().split('\n')
        current_question = None
        for line in question_lines:
            line = line.strip()
            if not line:
                continue # Skip empty lines

            if re.match(r'^\d+\.\s', line): # Start of a new question
                if current_question: # Save previous question if exists
                    questions.append(current_question)
                current_question = {'options': {}} # Start new question
                current_question['question'] = re.sub(r'\*\*|\*', '', line).strip() # Question text, remove bold
            elif re.match(r'^[A-D]\)\s', line) or re.match(r'^[A-D]\.\s', line): # Option line (handle both ')' and '.' after letter)
                if current_question:
                    option_letter = line[0]
                    option_text = line[2:].strip() # Remove "A) " or "A. "
                    current_question['options'][option_letter] = option_text
            elif re.match(r'^\*\*.*?\*\*$', line) or re.match(r'^\*.*?\*$', line) : # Check for title again, just in case after split. Remove bold title if found in lines.
                pass # Ignore title lines within question section if any.

        if current_question: # Append the last question
            questions.append(current_question)


        # --- Answer Key Parsing (No change needed) ---
        answer_lines = answer_key_section.strip().split('\n')
        for line in answer_lines:
            line = line.strip()
            match = re.match(r'(\d+)\.\s*([A-D])', line)
            if match:
                question_num = int(match.group(1)) - 1
                correct_answer = match.group(2)
                answer_key_dict[question_num] = correct_answer

        # Basic validation
        if not questions or not answer_key_dict:
            st.error("Error parsing quiz content. Please try again or check the generated format.")
            return None, None
        if len(questions) != len(answer_key_dict):
            st.warning(f"Number of questions parsed ({len(questions)}) does not match number of answers in answer key ({len(answer_key_dict)}). Parsing might be incomplete.")


        # Combine parsed questions and answer key into quiz_data
        quiz_data_list = []
        for i, q_data in enumerate(questions):
            correct_answer = answer_key_dict.get(i)
            if correct_answer:
                quiz_data_list.append({
                    'question': q_data['question'],
                    'options': q_data['options'],
                    'correct_answer': correct_answer
                })
            else:
                st.warning(f"Could not find correct answer for question {i+1} in the answer key.")
                return None, None

        return quiz_data_list, answer_key_dict


    def display_question():
        """Displays the current question and options for interactive quiz."""
        if st.session_state.quiz_data is None:
            st.error("Quiz data not loaded yet. Generate a quiz first.")
            return

        if st.session_state.current_question_index < len(st.session_state.quiz_data):
            question_data = st.session_state.quiz_data[st.session_state.current_question_index]
            question_number = st.session_state.current_question_index + 1
            st.subheader(f"{question_data['question']}")

            options_list = [f"{key}. {value}" for key, value in question_data['options'].items()]
            user_choice = st.radio("Choose an answer:", options_list, key=f"q_{question_number}") # Unique key for radio buttons

            if st.button("Submit Answer", key=f"submit_q_{question_number}"): # Unique key for submit button
                selected_option_letter = user_choice.split('.')[0] # Extract A, B, C, or D
                st.session_state.user_answers.append(selected_option_letter)

                if st.session_state.current_question_index < len(st.session_state.quiz_data) - 1:
                    st.session_state.current_question_index += 1
                else:
                    st.session_state.quiz_completed = True # Mark quiz as completed after last question
                st.rerun()


    def display_results():
        """Displays the quiz results after completion."""
        if st.session_state.quiz_completed:
            st.balloons()
            st.markdown("### Quiz Results")
            correct_count = 0
            for i in range(len(st.session_state.quiz_data)):
                user_answer = st.session_state.user_answers[i] if i < len(st.session_state.user_answers) else None # Handle if user didn't answer all
                correct_answer = st.session_state.quiz_data[i]['correct_answer']
                if user_answer == correct_answer:
                    correct_count += 1
                    result_text = f"**Question {i+1}:** ✅ Correct! (Your answer: {user_answer}, Correct answer: {correct_answer})"
                    color = "green"
                else:
                    result_text = f"**Question {i+1}:** ❌ Incorrect. (Your answer: {user_answer if user_answer else 'Not answered'}, Correct answer: {correct_answer})"
                    color = "red"
                st.markdown(f"<font color='{color}'>{result_text}</font>", unsafe_allow_html=True)

            st.divider()
            
            percentage_correct = (correct_count / len(st.session_state.quiz_data)) * 100
            if percentage_correct > 75:
                result_color = "#9AD8E1"
            else:
                result_color = "#E098B7"
            
            st.metric(label=f"**Final Score**", value=f"{correct_count} out of {len(st.session_state.quiz_data)}", delta=f"{percentage_correct:.2f}%", delta_color=result_color)
            # st.markdown(f"### Final Score: {correct_count} out of {len(st.session_state.quiz_data)} correct ({percentage_correct:.2f}%)")
            st.session_state.score = correct_count # Store score in session state


    # User input for topic - Conditional placement
    if not st.session_state.quiz_started:
        topic = st.text_input("Enter the topic for your quiz:")
    else:
        topic = st.sidebar.text_input("Quiz Topic", value=st.session_state.get('quiz_topic', '')) # Move to sidebar after quiz starts, keep topic value

    if topic and not st.session_state.quiz_started: # Only process topic and button if quiz hasn't started yet for this topic
        if st.button("Generate Quiz"):
            st.session_state.quiz_started = True # Set quiz started to true when generate button is clicked
            st.session_state.quiz_topic = topic # Store topic in session state to keep it in sidebar input
            with st.spinner(f"Generating quiz on '{topic}'..."):
                try:
                    prompt = f"""
                    Generate a multiple-choice quiz on the topic of "{topic}".
                    The quiz should have 5 questions.

                    **Formatting Instructions:**

                    1. **Question Numbering:**  Start each question with a number followed by a period (e.g., "1.").
                    2. **Question Text:**  Immediately after the question number, write the question text.
                    3. **Options:**
                        * List four options for each question, labeled A, B, C, and D.
                        * Use the format: "A) Option text", "B) Option text", etc. (letter followed by a parenthesis, then a space, then the option text).
                        * Place each option on a new line directly below the question.
                    4. **Answer Key:**
                        * After all questions, create a separate section titled "**Answer Key:**".
                        * In the Answer Key, list the correct answer for each question in the format: "1. Correct option letter", "2. Correct option letter", etc. (question number, period, space, correct option letter - A, B, C, or D).

                    **Example of Desired Format:**

                    **Quiz Title (Optional, but good to have)**

                    1. Question 1 text?
                    A) Option A
                    B) Option B
                    C) Option C
                    D) Option D

                    2. Question 2 text?
                    A) Option A
                    B) Option B
                    C) Option C
                    D) Option D

                    ... (and so on for 5 questions) ...

                    **Answer Key:**
                    1. C
                    2. A
                    3. B
                    4. D
                    5. A

                    Please generate the quiz in this exact format.
                    """
                    if st.session_state.selected_api == "Gemini API":
                        response = model.generate_content(prompt)
                        quiz_content = response.text
                    elif st.session_state.selected_api == "OpenAI API":
                        response = openai.ChatCompletion.create(
                            model=st.session_state.openai_model,
                            messages=[{"role": "user", "content": prompt}]
                        )
                        quiz_content = response.choices[0].message.content


                    parsed_quiz_data, answer_key = parse_quiz_content(quiz_content)


                    if quiz_content: # Check if quiz_content was generated successfully (outer if)
                        if parsed_quiz_data: # Check if parsing was successful (inner if)
                            st.session_state.quiz_data = parsed_quiz_data
                            st.session_state.current_question_index = 0
                            st.session_state.user_answers = []
                            st.session_state.quiz_completed = False
                            st.session_state.score = 0
                            st.success(f"Quiz on '{topic}' generated successfully! Let's begin.")
                        else: # else associated with inner if parsed_quiz_data
                            st.error("Failed to parse quiz content. Please try generating again.")
                            st.session_state.quiz_data = None
                    else: # else associated with outer if quiz_content
                        st.error("Failed to generate quiz content. Please try again or check your API key.")

                except Exception as e:
                    st.error(f"An error occurred: {e}")
                    st.error("Please check your API key and network connection. If the problem persists, try a different topic or try again later.")
            st.rerun() # Rerun to move topic input to sidebar

    # Quiz Display Logic
    if st.session_state.quiz_data:
        if not st.session_state.quiz_completed:
            display_question()
        else:
            display_results()
    elif topic and not st.session_state.quiz_data and not st.session_state.quiz_completed and not st.session_state.quiz_started: # Message if topic entered but quiz not generated yet and quiz not started
        st.info("Click 'Generate Quiz' to start the quiz.")

# --- Main App Flow Control ---
if 'logged_in' not in st.session_state:
    st.session_state.logged_in = False

if not st.session_state.logged_in:
    login_page()
else:
    quiz_app()