Jintonic92 commited on
Commit
b205088
ยท
verified ยท
1 Parent(s): b73fbfc

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +488 -0
app.py ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import os
4
+ from src.SecondModule.module2 import SimilarQuestionGenerator
5
+ from src.ThirdModule.module3 import AnswerVerifier
6
+ import logging
7
+ from typing import Optional, Tuple
8
+ #from latex_formatter import LatexFormatter # LaTeX ํฌ๋งทํ„ฐ import
9
+ from pylatexenc.latex2text import LatexNodes2Text
10
+ import re
11
+
12
+
13
+ logging.basicConfig(level=logging.DEBUG)
14
+
15
+
16
+ # Streamlit ํŽ˜์ด์ง€ ๊ธฐ๋ณธ ์„ค์ •
17
+ st.set_page_config(
18
+ page_title="MisconcepTutor",
19
+ layout="wide",
20
+ initial_sidebar_state="expanded"
21
+ )
22
+
23
+ @st.cache_resource
24
+ def load_answer_verifier():
25
+ """๋‹ต์•ˆ ๊ฒ€์ฆ ๋ชจ๋ธ ๋กœ๋“œ"""
26
+ from src.ThirdModule.module3 import AnswerVerifier
27
+ return AnswerVerifier()
28
+
29
+ # ๊ฒฝ๋กœ ์„ค์ •
30
+ base_path = os.path.dirname(os.path.abspath(__file__))
31
+ data_path = os.path.join(base_path, 'Data')
32
+ misconception_csv_path = os.path.join(data_path, 'misconception_mapping.csv')
33
+
34
+ # ๋กœ๊น… ์„ค์ •
35
+ logging.basicConfig(level=logging.INFO)
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # ์„ธ์…˜ ์ƒํƒœ ์ดˆ๊ธฐํ™” - ๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰๋˜๋„๋ก ์ตœ์ƒ๋‹จ์— ๋ฐฐ์น˜
39
+ if 'initialized' not in st.session_state:
40
+ st.session_state.initialized = True
41
+ st.session_state.wrong_questions = []
42
+ st.session_state.misconceptions = []
43
+ st.session_state.current_question_index = 0
44
+ st.session_state.generated_questions = []
45
+ st.session_state.current_step = 'initial'
46
+ st.session_state.selected_wrong_answer = None
47
+ st.session_state.questions = []
48
+ logger.info("Session state initialized")
49
+
50
+ # ๋ฌธ์ œ ์ƒ์„ฑ๊ธฐ ์ดˆ๊ธฐํ™”
51
+ @st.cache_resource
52
+ def load_question_generator():
53
+ """๋ฌธ์ œ ์ƒ์„ฑ ๋ชจ๋ธ ๋กœ๋“œ"""
54
+ if not os.path.exists(misconception_csv_path):
55
+ st.error(f"CSV ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {misconception_csv_path}")
56
+ raise FileNotFoundError(f"CSV ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {misconception_csv_path}")
57
+ return SimilarQuestionGenerator(misconception_csv_path=misconception_csv_path)
58
+
59
+ # CSV ๋ฐ์ดํ„ฐ ๋กœ๋“œ ํ•จ์ˆ˜
60
+ @st.cache_data
61
+ def load_data(data_file = '/train.csv'):
62
+ try:
63
+ file_path = os.path.join(data_path, data_file.lstrip('/'))
64
+ df = pd.read_csv(file_path)
65
+ logger.info(f"Data loaded successfully from {file_path}")
66
+ return df
67
+ except FileNotFoundError:
68
+ st.error(f"ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {data_file}")
69
+ logger.error(f"File not found: {data_file}")
70
+ return None
71
+
72
+ def start_quiz():
73
+ """ํ€ด์ฆˆ ์‹œ์ž‘ ๋ฐ ์ดˆ๊ธฐํ™”"""
74
+ df = load_data()
75
+ if df is None or df.empty:
76
+ st.error("๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ์…‹์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
77
+ return
78
+
79
+ st.session_state.questions = df.sample(n=10, random_state=42)
80
+ st.session_state.current_step = 'quiz'
81
+ st.session_state.current_question_index = 0
82
+ st.session_state.wrong_questions = []
83
+ st.session_state.misconceptions = []
84
+ st.session_state.generated_questions = []
85
+ logger.info("Quiz started")
86
+
87
+
88
+ def generate_similar_question(wrong_q, misconception_id, generator):
89
+ """์œ ์‚ฌ ๋ฌธ์ œ ์ƒ์„ฑ"""
90
+ logger.info(f"Generating similar question for misconception_id: {misconception_id}")
91
+
92
+ # ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
93
+ if not isinstance(wrong_q, dict):
94
+ logger.error(f"Invalid wrong_q type: {type(wrong_q)}")
95
+ st.error("์œ ์‚ฌ ๋ฌธ์ œ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ํ˜•์‹์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
96
+ return None
97
+
98
+ try:
99
+ # misconception_id๊ฐ€ ์—†๊ฑฐ๋‚˜ NaN์ธ ๊ฒฝ์šฐ ๋‹ค๋ฅธ misconception ์‚ฌ์šฉ
100
+ if pd.isna(misconception_id):
101
+ logger.info("Original misconception_id is NaN, trying to find alternative")
102
+ # ํ˜„์žฌ๊นŒ์ง€ ๋‚˜์˜จ misconception๋“ค ์ค‘์—์„œ ์„ ํƒ
103
+ available_misconceptions = [m for m in st.session_state.misconceptions if not pd.isna(m)]
104
+
105
+ if available_misconceptions:
106
+ # ๊ฐ€์žฅ ์ตœ๊ทผ์— ๋‚˜์˜จ misconception ์„ ํƒ
107
+ misconception_id = available_misconceptions[-1]
108
+ logger.info(f"Using alternative misconception_id: {misconception_id}")
109
+ else:
110
+ # ๊ธฐ๋ณธ misconception ID ์‚ฌ์šฉ (์˜ˆ: ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ misconception)
111
+ misconception_id = 2001 # ์ ์ ˆํ•œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ˆ˜์ • ํ•„์š”
112
+ logger.info(f"Using default misconception_id: {misconception_id}")
113
+
114
+ # ๋ฐ์ดํ„ฐ ์ค€๋น„ (ํŠœํ”Œ ๋ณ€ํ™˜ ๋ฐฉ์ง€)
115
+ input_data = {
116
+ 'construct_name': str(wrong_q.get('ConstructName', '')),
117
+ 'subject_name': str(wrong_q.get('SubjectName', '')),
118
+ 'question_text': str(wrong_q.get('QuestionText', '')),
119
+ 'correct_answer_text': str(wrong_q.get(f'Answer{wrong_q["CorrectAnswer"]}Text', '')),
120
+ 'wrong_answer_text': str(wrong_q.get(f'Answer{st.session_state.selected_wrong_answer}Text', '')),
121
+ 'misconception_id': int(misconception_id)
122
+ }
123
+
124
+ logger.info(f"Prepared input data: {input_data}")
125
+
126
+ with st.spinner("๐Ÿ“ ์œ ์‚ฌ ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค..."):
127
+ # ์œ ์‚ฌ ๋ฌธ์ œ ์ƒ์„ฑ ํ˜ธ์ถœ
128
+ generated_q, _ = generator.generate_similar_question_with_text(
129
+ construct_name=input_data['construct_name'],
130
+ subject_name=input_data['subject_name'],
131
+ question_text=input_data['question_text'],
132
+ correct_answer_text=input_data['correct_answer_text'],
133
+ wrong_answer_text=input_data['wrong_answer_text'],
134
+ misconception_id=input_data['misconception_id']
135
+ )
136
+
137
+ if generated_q:
138
+ verifier = load_answer_verifier()
139
+ with st.status("๐Ÿค” AI๊ฐ€ ๋ฌธ์ œ๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค..."):
140
+ st.write("๋‹ต์•ˆ์˜ ์ •ํ™•์„ฑ์„ ๊ฒ€์ฆํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค...")
141
+ verified_answer = verifier.verify_answer(
142
+ question=generated_q.question,
143
+ choices=generated_q.choices
144
+ )
145
+
146
+ if verified_answer:
147
+ logger.info(f"Answer verified: {verified_answer}")
148
+ st.write("โœ… ๊ฒ€์ฆ ์™„๋ฃŒ!")
149
+ result = {
150
+ 'question': generated_q.question,
151
+ 'choices': generated_q.choices,
152
+ 'correct': verified_answer,
153
+ 'explanation': generated_q.explanation
154
+ }
155
+ st.session_state['current_similar_question_answer'] = verified_answer
156
+ return result
157
+ else:
158
+ logger.warning("Answer verification failed, using original answer")
159
+ st.write("โš ๏ธ ๊ฒ€์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์›๋ณธ ๋‹ต์•ˆ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
160
+ result = {
161
+ 'question': generated_q.question,
162
+ 'choices': generated_q.choices,
163
+ 'correct': generated_q.correct_answer,
164
+ 'explanation': generated_q.explanation
165
+ }
166
+ st.session_state['current_similar_question_answer'] = generated_q.correct_answer
167
+ return result
168
+
169
+ except Exception as e:
170
+ logger.error(f"Error in generate_similar_question: {str(e)}")
171
+ st.error(f"๋ฌธ์ œ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}")
172
+ return None
173
+
174
+ return None
175
+
176
+ # ์ˆ˜์ •
177
+ def handle_answer(answer, current_q):
178
+ """๋‹ต๋ณ€ ์ฒ˜๋ฆฌ"""
179
+ if answer != current_q['CorrectAnswer']:
180
+ wrong_q_dict = current_q.to_dict()
181
+ st.session_state.wrong_questions.append(wrong_q_dict)
182
+ st.session_state.selected_wrong_answer = answer
183
+
184
+ misconception_key = f'Misconception{answer}Id'
185
+ misconception_id = current_q.get(misconception_key)
186
+ st.session_state.misconceptions.append(misconception_id)
187
+
188
+ st.session_state.current_question_index += 1
189
+ if st.session_state.current_question_index >= len(st.session_state.questions):
190
+ st.session_state.current_step = 'review'
191
+ else:
192
+ st.session_state.current_step = 'quiz'
193
+
194
+ # ์ˆ˜์ •
195
+ def display_math_content(content):
196
+ """
197
+ Display mathematical content with proper formatting.
198
+
199
+ Args:
200
+ content (str): The math content to display
201
+ """
202
+ # Convert LaTeX to plain text for display
203
+ from pylatexenc.latex2text import LatexNodes2Text
204
+
205
+ # Clean and format the content
206
+ formatted_content = LatexNodes2Text().latex_to_text(content)
207
+ st.markdown(f'<div class="math-container">{formatted_content}</div>', unsafe_allow_html=True)
208
+
209
+ # ์ถ”๊ฐ€
210
+ def add_custom_css():
211
+ st.markdown(
212
+ """
213
+ <style>
214
+ .problem-header {
215
+ color: #FF6B6B;
216
+ font-size: 24px;
217
+ font-weight: bold;
218
+ margin-bottom: 20px;
219
+ }
220
+
221
+ .math-container {
222
+ background-color: #f9f9f9;
223
+ padding: 20px;
224
+ border-radius: 10px;
225
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
226
+ margin-bottom: 20px;
227
+ }
228
+
229
+ .options-container {
230
+ display: grid;
231
+ grid-template-columns: 1fr 1fr;
232
+ gap: 20px;
233
+ }
234
+
235
+ .option {
236
+ background-color: white;
237
+ border: 1px solid #ddd;
238
+ border-radius: 8px;
239
+ padding: 15px;
240
+ text-align: center;
241
+ font-size: 18px;
242
+ cursor: pointer;
243
+ transition: background-color 0.3s;
244
+ }
245
+
246
+ .option:hover {
247
+ background-color: #f8f9fa;
248
+ }
249
+ </style>
250
+ """,
251
+ unsafe_allow_html=True
252
+ )
253
+
254
+ def display_question(question, answers):
255
+ """Display question and options with LaTeX formatting"""
256
+ st.markdown('<div class="problem-header">Problem:</div>', unsafe_allow_html=True)
257
+ display_math_content(question)
258
+
259
+ col1, col2 = st.columns(2)
260
+ for i, (col, opt) in enumerate([(col1, 'A'), (col1, 'C'), (col2, 'B'), (col2, 'D')]):
261
+ with col:
262
+ st.markdown(f"### {opt})")
263
+ display_option_content(answers[opt])
264
+ if st.button("์„ ํƒ", key=f"btn_{opt}"):
265
+ handle_answer(opt, st.session_state.questions.iloc[st.session_state.current_question_index])
266
+ st.rerun()
267
+
268
+ def display_option_content(option_text):
269
+ """Process and display option content with LaTeX formatting"""
270
+ from pylatexenc.latex2text import LatexNodes2Text
271
+ formatted_content = LatexNodes2Text().latex_to_text(option_text)
272
+ st.markdown(f'<div class="math-container">{formatted_content}</div>', unsafe_allow_html=True)
273
+
274
+ # ์ถ”๊ฐ€
275
+ def update_similar_question_display(new_question, i, answered=False):
276
+ """Display similar question and its options"""
277
+ display_math_content(new_question['question'])
278
+
279
+ col1, col2 = st.columns(2)
280
+ for j, (col, opt) in enumerate([(col1, 'A'), (col1, 'C'), (col2, 'B'), (col2, 'D')]):
281
+ with col:
282
+ st.markdown(f"### {opt})")
283
+ display_option_content(new_question['choices'][opt])
284
+ if not answered:
285
+ if st.button("์„ ํƒ", key=f"similar_{opt}_{i}"):
286
+ handle_similar_answer(opt, i)
287
+ st.rerun()
288
+
289
+ def main():
290
+ """๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง"""
291
+ st.title("MisconcepTutor")
292
+
293
+ # Generator ์ดˆ๊ธฐํ™”
294
+ generator = load_question_generator()
295
+
296
+ # ์ดˆ๊ธฐ ํ™”๋ฉด
297
+ if st.session_state.current_step == 'initial':
298
+ st.write("#### ํ•™์Šต์„ ์‹œ์ž‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 10๊ฐœ์˜ ๋ฌธ์ œ๋ฅผ ํ’€์–ด๋ณผ๊นŒ์š”?")
299
+ if st.button("ํ•™์Šต ์‹œ์ž‘", key="start_quiz"):
300
+ start_quiz()
301
+ st.rerun()
302
+
303
+ # ํ€ด์ฆˆ ํ™”๋ฉด
304
+ elif st.session_state.current_step == 'quiz':
305
+ current_q = st.session_state.questions.iloc[st.session_state.current_question_index]
306
+
307
+ # ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
308
+ progress = st.session_state.current_question_index / 10
309
+ st.progress(progress)
310
+ st.write(f"### ๋ฌธ์ œ {st.session_state.current_question_index + 1}/10")
311
+
312
+ # ๋ฌธ์ œ ํ‘œ์‹œ
313
+ st.markdown("---")
314
+ question_row = current_q['QuestionText']
315
+ question_text = LatexNodes2Text().latex_to_text(current_q['QuestionText'])
316
+
317
+ answers ={
318
+ 'A': current_q['AnswerAText'],
319
+ 'B': current_q['AnswerBText'],
320
+ 'C': current_q['AnswerCText'],
321
+ 'D': current_q['AnswerDText']
322
+ }
323
+
324
+ display_question(question_text, answers)
325
+
326
+
327
+ # ๋ณต์Šต ํ™”๋ฉด
328
+ elif st.session_state.current_step == 'review':
329
+ st.write("### ํ•™์Šต ๊ฒฐ๊ณผ")
330
+
331
+ # ๊ฒฐ๊ณผ ํ†ต๊ณ„
332
+ col1, col2, col3 = st.columns(3)
333
+ col1.metric("์ด ๋ฌธ์ œ ์ˆ˜", 10)
334
+ col2.metric("๋งž์€ ๋ฌธ์ œ", 10 - len(st.session_state.wrong_questions))
335
+ col3.metric("ํ‹€๋ฆฐ ๋ฌธ์ œ", len(st.session_state.wrong_questions))
336
+
337
+ # ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
338
+ if len(st.session_state.wrong_questions) == 0:
339
+ st.balloons() # ์ถ•ํ•˜ ํšจ๊ณผ
340
+ st.success("๐ŸŽ‰ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ๋งž์ถ”์…จ์–ด์š”!")
341
+ st.markdown("""
342
+ ### ๐Ÿ† ์ˆ˜ํ•™์™•์ด์‹ญ๋‹ˆ๋‹ค!
343
+ ์™„๋ฒฝํ•œ ์ ์ˆ˜๋ฅผ ๋ฐ›์œผ์…จ๋„ค์š”! ์ˆ˜ํ•™์  ๊ฐœ๋…์„ ์ •ํ™•ํ•˜๊ฒŒ ์ดํ•ดํ•˜๊ณ  ๊ณ„์‹  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
344
+ """)
345
+ elif len(st.session_state.wrong_questions) <= 3:
346
+ st.success("์ž˜ ํ•˜์…จ์–ด์š”! ์กฐ๊ธˆ๋งŒ ๋” ์—ฐ์Šตํ•˜๋ฉด ์™„๋ฒฝํ•  ๊ฑฐ์˜ˆ์š”!")
347
+ else:
348
+ st.info("์ฒœ์ฒœํžˆ ๊ฐœ๋…์„ ๋ณต์Šตํ•ด๋ณด์•„์š”. ์—ฐ์Šตํ•˜๋‹ค ๋ณด๋ฉด ๋Š˜์–ด๋‚  ๊ฑฐ์˜ˆ์š”!")
349
+
350
+ # ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฒ„ํŠผ
351
+ col1, col2 = st.columns(2)
352
+ with col1:
353
+ if st.button("๐Ÿ”„ ์ƒˆ๋กœ์šด ๋ฌธ์ œ ์„ธํŠธ ์‹œ์ž‘ํ•˜๊ธฐ", use_container_width=True):
354
+ start_quiz()
355
+ st.rerun()
356
+ with col2:
357
+ if st.button("๐Ÿ  ์ฒ˜์Œ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ", use_container_width=True):
358
+ st.session_state.clear()
359
+ st.rerun()
360
+
361
+ # ํ‹€๋ฆฐ ๋ฌธ์ œ ๋ถ„์„ ๋ถ€๋ถ„
362
+ if st.session_state.wrong_questions:
363
+ st.write("### โœ๏ธ ํ‹€๋ฆฐ ๋ฌธ์ œ ๋ถ„์„")
364
+ tabs = st.tabs([f"๐Ÿ“ ํ‹€๋ฆฐ ๋ฌธ์ œ #{i + 1}" for i in range(len(st.session_state.wrong_questions))])
365
+
366
+ for i, (tab, (wrong_q, misconception_id)) in enumerate(zip(
367
+ tabs,
368
+ zip(st.session_state.wrong_questions, st.session_state.misconceptions)
369
+ )):
370
+ with tab:
371
+ st.write("**๐Ÿ“‹ ๋ฌธ์ œ:**")
372
+ display_math_content(wrong_q['QuestionText']) # ๋ฌธ์ œ ๋ Œ๋”๋ง
373
+ st.write("**โœ… ์ •๋‹ต:**")
374
+ #display_math_content(wrong_q[f'Answer{wrong_q["CorrectAnswer"]}Text']) # ์ •๋‹ต ๋ Œ๏ฟฝ๏ฟฝ๋ง
375
+ display_option_content(wrong_q[f'Answer{wrong_q["CorrectAnswer"]}Text'])
376
+
377
+ st.write("---")
378
+ st.write("**๐Ÿ” ๊ด€๋ จ๋œ Misconception:**")
379
+ if misconception_id and not pd.isna(misconception_id):
380
+ misconception_text = generator.get_misconception_text(misconception_id)
381
+ st.info(f"Misconception ID: {int(misconception_id)}\n\n{misconception_text}")
382
+ else:
383
+ st.info("Misconception ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
384
+
385
+ if st.button(f"๐Ÿ“š ์œ ์‚ฌ ๋ฌธ์ œ ํ’€๊ธฐ", key=f"retry_{i}"):
386
+ st.session_state[f"show_similar_question_{i}"] = True
387
+ st.session_state[f"similar_question_answered_{i}"] = False
388
+ st.rerun()
389
+
390
+ if st.session_state.get(f"show_similar_question_{i}", False):
391
+ st.divider()
392
+ new_question = generate_similar_question(wrong_q, misconception_id, generator)
393
+ if new_question:
394
+ st.write("### ๐ŸŽฏ ์œ ์‚ฌ ๋ฌธ์ œ")
395
+ display_math_content(new_question['question']) # ํ•จ์ˆ˜ ๊ต์ฒด
396
+
397
+ # ๋‹ต๋ณ€ ์ƒํƒœ ํ™•์ธ
398
+ answered = st.session_state.get(f"similar_question_answered_{i}", False)
399
+ update_similar_question_display(new_question, i, answered)
400
+
401
+ # ๋ณด๊ธฐ ํ‘œ์‹œ
402
+ st.write("**๋ณด๊ธฐ:**")
403
+ col1, col2 = st.columns(2)
404
+
405
+
406
+ # ๋‹ต๋ณ€ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋งŒ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
407
+ if not answered:
408
+ with col1:
409
+ for option in ['A', 'C']:
410
+ if st.button(
411
+ f"{option}) {LatexNodes2Text().latex_to_text(new_question['choices'][option])}",
412
+ key=f"similar_{option}_{i}"
413
+ ):
414
+ st.session_state[f"similar_question_answered_{i}"] = True
415
+ st.session_state[f"selected_answer_{i}"] = option
416
+ correct_answer = st.session_state.get('current_similar_question_answer')
417
+ if option == correct_answer:
418
+ st.session_state[f"is_correct_{i}"] = True
419
+ else:
420
+ st.session_state[f"is_correct_{i}"] = False
421
+ st.rerun()
422
+
423
+ with col2:
424
+ for option in ['B', 'D']:
425
+ if st.button(
426
+ f"{option}) {LatexNodes2Text().latex_to_text(new_question['choices'][option])}",
427
+ key=f"similar_{option}_{i}"
428
+ ):
429
+ st.session_state[f"similar_question_answered_{i}"] = True
430
+ st.session_state[f"selected_answer_{i}"] = option
431
+ correct_answer = st.session_state.get('current_similar_question_answer')
432
+ if option == correct_answer:
433
+ st.session_state[f"is_correct_{i}"] = True
434
+ else:
435
+ st.session_state[f"is_correct_{i}"] = False
436
+ st.rerun()
437
+
438
+ # ๋‹ต๋ณ€ํ•œ ๊ฒฝ์šฐ ๊ฒฐ๊ณผ ํ‘œ์‹œ
439
+ if answered:
440
+ is_correct = st.session_state.get(f"is_correct_{i}", False)
441
+ correct_answer = st.session_state.get('current_similar_question_answer')
442
+ if is_correct:
443
+ st.success("โœ… ์ •๋‹ต์ž…๋‹ˆ๋‹ค!")
444
+ else:
445
+ st.error(f"โŒ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค. ์ •๋‹ต์€ {correct_answer}์ž…๋‹ˆ๋‹ค.")
446
+
447
+ # ํ•ด์„ค ํ‘œ์‹œ
448
+ st.write("---")
449
+ st.write("**๐Ÿ“ ํ•ด์„ค:**", new_question['explanation'])
450
+
451
+ # ๋‹ค์‹œ ํ’€๊ธฐ ๋ฒ„ํŠผ
452
+ if st.button("๐Ÿ”„ ๋‹ค์‹œ ํ’€๊ธฐ", key=f"reset_{i}"):
453
+ st.session_state[f"similar_question_answered_{i}"] = False
454
+ st.session_state[f"selected_answer_{i}"] = None
455
+ st.session_state[f"is_correct_{i}"] = None
456
+ st.rerun()
457
+
458
+ # ๋ฌธ์ œ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ
459
+ if st.button("โŒ ๋ฌธ์ œ ๋‹ซ๊ธฐ", key=f"close_{i}"):
460
+ st.session_state[f"show_similar_question_{i}"] = False
461
+ st.session_state[f"similar_question_answered_{i}"] = False
462
+ st.session_state[f"selected_answer_{i}"] = None
463
+ st.session_state[f"is_correct_{i}"] = None
464
+ st.rerun()
465
+
466
+ # ํ™”๋ฉด ์•„๋ž˜ ์—ฌ๋ฐฑ ์ถ”๊ฐ€
467
+ st.markdown("<br>" * 5, unsafe_allow_html=True) # 5์ค„์˜ ๋นˆ ์ค„ ์ถ”๊ฐ€
468
+ st.markdown("""
469
+ <div style="height: 100px;">
470
+ </div>
471
+ """, unsafe_allow_html=True) # ์ถ”๊ฐ€ ์—ฌ๋ฐฑ
472
+ else:
473
+ st.error("์œ ์‚ฌ ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
474
+ if st.button("โŒ ๋‹ซ๊ธฐ", key=f"close_error_{i}"):
475
+ st.session_state[f"show_similar_question_{i}"] = False
476
+ st.rerun()
477
+ # ํ™”๋ฉด ์•„๋ž˜ ์—ฌ๋ฐฑ ์ถ”๊ฐ€
478
+ st.markdown("<br>" * 5, unsafe_allow_html=True) # 5์ค„์˜ ๋นˆ ์ค„ ์ถ”๊ฐ€
479
+ st.markdown("""
480
+ <div style="height: 100px;">
481
+ </div>
482
+ """, unsafe_allow_html=True) # ์ถ”๊ฐ€ ์—ฌ๋ฐฑ
483
+ if __name__ == "__main__":
484
+ main()
485
+
486
+ # random_state 42์—์„œ ์ •๋‹ต
487
+ # D C A A C
488
+ # A B B B B