Jintonic92's picture
Update app.py
3b212d5 verified
import streamlit as st
import pandas as pd
import os
from src.FisrtModule.module1 import MisconceptionModel
from src.SecondModule.module2 import SimilarQuestionGenerator
from src.ThirdModule.module3 import AnswerVerifier
import logging
from typing import Optional, Tuple
from pylatexenc.latex2text import LatexNodes2Text
import re
logging.basicConfig(level=logging.DEBUG)
# Initialize Misconception Model
@st.cache_resource
def load_misconception_model():
return MisconceptionModel(
model_name="minsuas/Misconceptions__1",
misconception_mapping_path=os.path.join(data_path, 'misconception_mapping.parquet'),
misconception_embs_paths=[os.path.join(data_path, f'embs_misconception-9-9.npy')]
)
# Streamlit ํŽ˜์ด์ง€ ๊ธฐ๋ณธ ์„ค์ •
st.set_page_config(
page_title="MisconcepTutor",
layout="wide",
initial_sidebar_state="expanded"
)
@st.cache_resource
def load_answer_verifier():
"""๋‹ต์•ˆ ๊ฒ€์ฆ ๋ชจ๋ธ ๋กœ๋“œ"""
from src.ThirdModule.module3 import AnswerVerifier
return AnswerVerifier()
# ๊ฒฝ๋กœ ์„ค์ •
base_path = os.path.dirname(os.path.abspath(__file__))
data_path = os.path.join(base_path, 'Data')
misconception_csv_path = os.path.join(data_path, 'misconception_mapping.csv')
# ๋กœ๊น… ์„ค์ •
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ์„ธ์…˜ ์ƒํƒœ ์ดˆ๊ธฐํ™” - ๊ฐ€์žฅ ๋จผ์ € ์‹คํ–‰๋˜๋„๋ก ์ตœ์ƒ๋‹จ์— ๋ฐฐ์น˜
if 'initialized' not in st.session_state:
st.session_state.initialized = True
st.session_state.wrong_questions = []
st.session_state.misconceptions = []
st.session_state.current_question_index = 0
st.session_state.generated_questions = []
st.session_state.current_step = 'initial'
st.session_state.selected_wrong_answer = None
st.session_state.questions = []
logger.info("Session state initialized")
# ๋ฌธ์ œ ์ƒ์„ฑ๊ธฐ ์ดˆ๊ธฐํ™”
@st.cache_resource
def load_question_generator():
"""๋ฌธ์ œ ์ƒ์„ฑ ๋ชจ๋ธ ๋กœ๋“œ"""
if not os.path.exists(misconception_csv_path):
st.error(f"CSV ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {misconception_csv_path}")
raise FileNotFoundError(f"CSV ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {misconception_csv_path}")
return SimilarQuestionGenerator(misconception_csv_path=misconception_csv_path)
# CSV ๋ฐ์ดํ„ฐ ๋กœ๋“œ ํ•จ์ˆ˜
@st.cache_data
def load_data(data_file='/train.csv', selected_indexes=None):
#def load_data(data_file='/processed_mathqa2.csv', selected_indexes=None):
try:
file_path = os.path.join(data_path, data_file.lstrip('/'))
df = pd.read_csv(file_path)
logger.info(f"Data loaded successfully from {file_path}")
if selected_indexes is not None:
#df = df.loc[selected_indexes] # ์‹ ๊ทœ ๋ฌธ์ œ
df = df.loc[df['QuestionId'].isin(selected_indexes)] # QuestionId ๊ธฐ์ค€ ํ•„ํ„ฐ๋ง
logger.info(f"Data filtered to selected indexes: {selected_indexes}")
# ํ•„ํ„ฐ๋ง ํ›„ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ค‘๋ณต ์ฒดํฌ
if df.duplicated(subset=['QuestionText']).any():
df = df.drop_duplicates(subset=['QuestionText'], keep='first')
logger.warning("Removed duplicates from selected indexes")
return df
except FileNotFoundError:
st.error(f"ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {data_file}")
logger.error(f"File not found: {data_file}")
return None
def start_quiz():
"""ํ€ด์ฆˆ ์‹œ์ž‘ ๋ฐ ์ดˆ๊ธฐํ™”"""
#selected_indexes = [2519, 3852, 3404, 3896, 7602, 3946, 12977, 1878, 7602, 3589, 9]
# 12038 ๋ฌธ์ œ ๋Š๊น€? # 1302 ๋™์ผ ๋ฌธ์ œ์ธ๊ฐ€? # 3473 ์•ˆ๋‚˜์˜ด? # 3887 ๋‹ต์•ˆ ํ‹€๋ฆผ # 9699 ์ˆ˜์‹ ์ด์ƒ # 9752 ๋ฌธ์ œ ๋Š๊น€
# train.csv
selected_indexes = [1866, 1864, 1845, 1862, 1861, 1829, 1827, 1802, 1741, 1725] # 1671]
# ํ™•์ • : 1671, 1725
# ํ†ต๊ณผ : 1864, 1866, 1845, 1861, 1862, 1802, 1827, 1829, 1741,
# ์„ธ๋ชจ : 1825,
# ๋ฌธ์ œ ์•ˆ์ด์จ : 1868, 1847, 1834, 1841, 1809, 1804, 1672, 1731, 1736, 1746, 1692, 1775, 1781
# ๋ฌธ์ œ ์ด์ƒ : 1792, 1804, 1813, 1679, 1711
df = load_data(selected_indexes=selected_indexes)
if df is None or df.empty:
st.error("๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ์…‹์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
return
#st.session_state.questions = df.sample(n=10, random_state=42)
st.session_state.questions = df
st.session_state.current_step = 'quiz'
st.session_state.current_question_index = 0
st.session_state.wrong_questions = []
st.session_state.misconceptions = []
st.session_state.generated_questions = []
logger.info("Quiz started")
def generate_similar_question(wrong_q, misconception_id, generator):
"""์œ ์‚ฌ ๋ฌธ์ œ ์ƒ์„ฑ"""
logger.info(f"Generating similar question for misconception_id: {misconception_id}")
# ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
if not isinstance(wrong_q, dict):
logger.error(f"Invalid wrong_q type: {type(wrong_q)}")
st.error("์œ ์‚ฌ ๋ฌธ์ œ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ํ˜•์‹์ด ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
return None
try:
# misconception_id๊ฐ€ ์—†๊ฑฐ๋‚˜ NaN์ธ ๊ฒฝ์šฐ ๋‹ค๋ฅธ misconception ์‚ฌ์šฉ
if pd.isna(misconception_id):
logger.info("Original misconception_id is NaN, trying to find alternative")
# ํ˜„์žฌ๊นŒ์ง€ ๋‚˜์˜จ misconception๋“ค ์ค‘์—์„œ ์„ ํƒ
available_misconceptions = [m for m in st.session_state.misconceptions if not pd.isna(m)]
if available_misconceptions:
# ๊ฐ€์žฅ ์ตœ๊ทผ์— ๋‚˜์˜จ misconception ์„ ํƒ
misconception_id = available_misconceptions[-1]
logger.info(f"Using alternative misconception_id: {misconception_id}")
else:
# ๊ธฐ๋ณธ misconception ID ์‚ฌ์šฉ (์˜ˆ: ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ misconception)
misconception_id = 2001 # ์ ์ ˆํ•œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ˆ˜์ • ํ•„์š”
logger.info(f"Using default misconception_id: {misconception_id}")
# ๋ฐ์ดํ„ฐ ์ค€๋น„ (ํŠœํ”Œ ๋ณ€ํ™˜ ๋ฐฉ์ง€)
input_data = {
'construct_name': str(wrong_q.get('ConstructName', '')),
'subject_name': str(wrong_q.get('SubjectName', '')),
'question_text': str(wrong_q.get('QuestionText', '')),
'correct_answer_text': str(wrong_q.get(f'Answer{wrong_q["CorrectAnswer"]}Text', '')),
'wrong_answer_text': str(wrong_q.get(f'Answer{st.session_state.selected_wrong_answer}Text', '')),
'misconception_id': int(misconception_id)
}
logger.info(f"Prepared input data: {input_data}")
with st.spinner("๐Ÿ“ ์œ ์‚ฌ ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค..."):
# ์œ ์‚ฌ ๋ฌธ์ œ ์ƒ์„ฑ ํ˜ธ์ถœ
generated_q, _ = generator.generate_similar_question_with_text(
construct_name=input_data['construct_name'],
subject_name=input_data['subject_name'],
question_text=input_data['question_text'],
correct_answer_text=input_data['correct_answer_text'],
wrong_answer_text=input_data['wrong_answer_text'],
misconception_id=input_data['misconception_id']
)
if generated_q:
verifier = load_answer_verifier()
with st.status("๐Ÿค” AI๊ฐ€ ๋ฌธ์ œ๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค..."):
st.write("๋‹ต์•ˆ์˜ ์ •ํ™•์„ฑ์„ ๊ฒ€์ฆํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค...")
verified_answer = verifier.verify_answer(
question=generated_q.question,
choices=generated_q.choices
)
if verified_answer:
logger.info(f"Answer verified: {verified_answer}")
st.write("โœ… ๊ฒ€์ฆ ์™„๋ฃŒ!")
result = {
'question': generated_q.question,
'choices': generated_q.choices,
'correct': verified_answer,
'explanation': generated_q.explanation
}
st.session_state['current_similar_question_answer'] = verified_answer
return result
else:
logger.warning("Answer verification failed, using original answer")
st.write("โš ๏ธ ๊ฒ€์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์›๋ณธ ๋‹ต์•ˆ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
result = {
'question': generated_q.question,
'choices': generated_q.choices,
'correct': generated_q.correct_answer,
'explanation': generated_q.explanation
}
st.session_state['current_similar_question_answer'] = generated_q.correct_answer
return result
except Exception as e:
logger.error(f"Error in generate_similar_question: {str(e)}")
st.error(f"๋ฌธ์ œ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}")
return None
return None
def handle_answer(answer, current_q):
"""๋‹ต๋ณ€ ์ฒ˜๋ฆฌ"""
if answer != current_q['CorrectAnswer']:
wrong_q_dict = current_q.to_dict()
st.session_state.wrong_questions.append(wrong_q_dict)
st.session_state.selected_wrong_answer = answer
misconception_key = f'Misconception{answer}Id'
misconception_id = current_q.get(misconception_key)
st.session_state.misconceptions.append(misconception_id)
st.session_state.current_question_index += 1
if st.session_state.current_question_index >= len(st.session_state.questions):
st.session_state.current_step = 'review'
else:
st.session_state.current_step = 'quiz'
def display_math_content(content):
"""
Display mathematical content with proper formatting.
Args:
content (str): The math content to display
"""
# Convert LaTeX to plain text for display
from pylatexenc.latex2text import LatexNodes2Text
# Clean and format the content
formatted_content = LatexNodes2Text().latex_to_text(content)
st.markdown(f'<div class="math-container">{formatted_content}</div>', unsafe_allow_html=True)
def add_custom_css():
st.markdown(
"""
<style>
.problem-header {
color: #FF6B6B;
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}
.math-container {
background-color: #f0f8ff;
padding: 15px 20px;
border-radius: 5px;
margin: 5px 0;
}
button {
color: #0066ff;
font-weight: 500;
}
</style>
""",
unsafe_allow_html=True
)
def display_question(question, answers):
"""Display question and options with LaTeX formatting"""
st.markdown('<div class="problem-header">Problem:</div>', unsafe_allow_html=True)
display_math_content(question)
# Add custom CSS for options
st.markdown("""
<style>
.option-container {
background-color: #f0f8ff;
padding: 10px 20px;
margin: 5px 0;
border-radius: 5px;
cursor: pointer;
display: flex;
align-items: center;
gap: 20px;
}
.option-text {
color: #0066ff;
font-weight: 500;
width: 30px;
}
</style>
""", unsafe_allow_html=True)
# Display options
for opt in ['A', 'B', 'C', 'D']:
with st.container():
col1, col2 = st.columns([1, 11])
with col1:
if st.button(f"{opt}.", key=f"btn_{opt}", help="Click to select"):
handle_answer(opt, st.session_state.questions.iloc[st.session_state.current_question_index])
st.rerun()
with col2:
display_option_content(answers[opt])
def display_option_content(option_text):
"""Process and display option content with LaTeX formatting"""
from pylatexenc.latex2text import LatexNodes2Text
formatted_content = LatexNodes2Text().latex_to_text(option_text)
st.markdown(f'<div class="math-container">{formatted_content}</div>', unsafe_allow_html=True)
def update_similar_question_display(new_question, i, answered=False):
"""Display similar question and its options"""
display_math_content(new_question['question'])
# Display options
for opt in ['A', 'B', 'C', 'D']:
with st.container():
col1, col2 = st.columns([1, 11])
with col1:
if st.button(f"{opt}.", key=f"sim_btn_{opt}_{i}", help="Click to select"):
if not answered:
# ์„ ํƒํ•œ ์˜ต์…˜(opt)์„ st.session_state์— ์ €์žฅ
st.session_state[f"similar_question_answered_{i}"] = True
st.session_state[f"selected_answer_{i}"] = opt
correct_answer = st.session_state.get('current_similar_question_answer')
# ์ •๋‹ต ์—ฌ๋ถ€๋ฅผ ํ™•์ธ
st.session_state[f"is_correct_{i}"] = (opt == correct_answer)
st.rerun()
with col2:
display_option_content(new_question['choices'][opt])
def main():
"""๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง"""
st.title("MisconcepTutor")
# Misconception Model ๋กœ๋“œ
misconception_model = load_misconception_model()
# Generator ์ดˆ๊ธฐํ™”
generator = load_question_generator()
add_custom_css()
# ์ดˆ๊ธฐ ํ™”๋ฉด
if st.session_state.current_step == 'initial':
st.write("#### ํ•™์Šต์„ ์‹œ์ž‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 10๊ฐœ์˜ ๋ฌธ์ œ๋ฅผ ํ’€์–ด๋ณผ๊นŒ์š”?")
if st.button("ํ•™์Šต ์‹œ์ž‘", key="start_quiz"):
start_quiz()
st.rerun()
# ํ€ด์ฆˆ ํ™”๋ฉด
elif st.session_state.current_step == 'quiz':
current_q = st.session_state.questions.iloc[st.session_state.current_question_index]
# ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
progress = st.session_state.current_question_index / 10
st.progress(progress)
st.write(f"### ๋ฌธ์ œ {st.session_state.current_question_index + 1}/10")
# ๋ฌธ์ œ ํ‘œ์‹œ
st.markdown("---")
question_row = current_q['QuestionText']
question_text = LatexNodes2Text().latex_to_text(current_q['QuestionText'])
answers ={
'A': current_q['AnswerAText'],
'B': current_q['AnswerBText'],
'C': current_q['AnswerCText'],
'D': current_q['AnswerDText']
}
display_question(question_text, answers)
# ๋ณต์Šต ํ™”๋ฉด
elif st.session_state.current_step == 'review':
st.write("### ํ•™์Šต ๊ฒฐ๊ณผ")
# ๊ฒฐ๊ณผ ํ†ต๊ณ„
col1, col2, col3 = st.columns(3)
col1.metric("์ด ๋ฌธ์ œ ์ˆ˜", 10)
col2.metric("๋งž์€ ๋ฌธ์ œ", 10 - len(st.session_state.wrong_questions))
col3.metric("ํ‹€๋ฆฐ ๋ฌธ์ œ", len(st.session_state.wrong_questions))
# ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
if len(st.session_state.wrong_questions) == 0:
st.balloons() # ์ถ•ํ•˜ ํšจ๊ณผ
st.success("๐ŸŽ‰ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ๋งž์ถ”์…จ์–ด์š”!")
st.markdown("""
### ๐Ÿ† ์ˆ˜ํ•™์™•์ด์‹ญ๋‹ˆ๋‹ค!
์™„๋ฒฝํ•œ ์ ์ˆ˜๋ฅผ ๋ฐ›์œผ์…จ๋„ค์š”! ์ˆ˜ํ•™์  ๊ฐœ๋…์„ ์ •ํ™•ํ•˜๊ฒŒ ์ดํ•ดํ•˜๊ณ  ๊ณ„์‹  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
""")
elif len(st.session_state.wrong_questions) <= 3:
st.success("์ž˜ ํ•˜์…จ์–ด์š”! ์กฐ๊ธˆ๋งŒ ๋” ์—ฐ์Šตํ•˜๋ฉด ์™„๋ฒฝํ•  ๊ฑฐ์˜ˆ์š”!")
else:
st.info("์ฒœ์ฒœํžˆ ๊ฐœ๋…์„ ๋ณต์Šตํ•ด๋ณด์•„์š”. ์—ฐ์Šตํ•˜๋‹ค ๋ณด๋ฉด ๋Š˜์–ด๋‚  ๊ฑฐ์˜ˆ์š”!")
# ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฒ„ํŠผ
col1, col2 = st.columns(2)
with col1:
if st.button("๐Ÿ”„ ์ƒˆ๋กœ์šด ๋ฌธ์ œ ์„ธํŠธ ์‹œ์ž‘ํ•˜๊ธฐ", use_container_width=True):
start_quiz()
st.rerun()
with col2:
if st.button("๐Ÿ  ์ฒ˜์Œ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ", use_container_width=True):
st.session_state.clear()
st.rerun()
# ํ‹€๋ฆฐ ๋ฌธ์ œ ๋ถ„์„ ๋ถ€๋ถ„
if st.session_state.wrong_questions:
st.write("### โœ๏ธ ํ‹€๋ฆฐ ๋ฌธ์ œ ๋ถ„์„")
tabs = st.tabs([f"๐Ÿ“ ํ‹€๋ฆฐ ๋ฌธ์ œ #{i + 1}" for i in range(len(st.session_state.wrong_questions))])
for i, (tab, (wrong_q, misconception_id)) in enumerate(zip(
tabs,
zip(st.session_state.wrong_questions, st.session_state.misconceptions)
)):
with tab:
st.write("**๐Ÿ“‹ ๋ฌธ์ œ:**")
display_math_content(wrong_q['QuestionText']) # ๋ฌธ์ œ ๋ Œ๋”๋ง
st.write("**โœ… ์ •๋‹ต:**")
display_option_content(wrong_q[f'Answer{wrong_q["CorrectAnswer"]}Text'])
st.write("---")
st.write("**๐Ÿ” ๊ด€๋ จ๋œ Misconception:**")
if misconception_id and not pd.isna(misconception_id):
misconception_text = generator.get_misconception_text(misconception_id)
st.info(f"Misconception ID: {int(misconception_id)}\n\n{misconception_text}")
else:
st.info("Misconception ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
if st.button(f"๐Ÿ“š ์œ ์‚ฌ ๋ฌธ์ œ ํ’€๊ธฐ", key=f"retry_{i}"):
st.session_state[f"show_similar_question_{i}"] = True
st.session_state[f"similar_question_answered_{i}"] = False
st.rerun()
if st.session_state.get(f"show_similar_question_{i}", False):
st.divider()
new_question = generate_similar_question(wrong_q, misconception_id, generator)
if new_question:
st.write("### ๐ŸŽฏ ์œ ์‚ฌ ๋ฌธ์ œ")
#display_math_content(new_question['question']) # ํ•จ์ˆ˜ ๊ต์ฒด
# ๋‹ต๋ณ€ ์ƒํƒœ ํ™•์ธ
answered = st.session_state.get(f"similar_question_answered_{i}", False)
#update_similar_question_display(new_question, i, answered)
# ์„ ํƒํ•œ ์˜ต์…˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ
update_similar_question_display(new_question, i)
# ๋‹ต๋ณ€ํ•œ ๊ฒฝ์šฐ ๊ฒฐ๊ณผ ํ‘œ์‹œ
if answered:
is_correct = st.session_state.get(f"is_correct_{i}", False)
correct_answer = st.session_state.get('current_similar_question_answer')
if is_correct:
st.success("โœ… ์ •๋‹ต์ž…๋‹ˆ๋‹ค!")
else:
st.error(f"โŒ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค. ์ •๋‹ต์€ {correct_answer}์ž…๋‹ˆ๋‹ค.")
# ํ•ด์„ค ํ‘œ์‹œ
st.write("---")
st.write("**๐Ÿ“ ํ•ด์„ค:**", new_question['explanation'])
# ๋‹ค์‹œ ํ’€๊ธฐ ๋ฒ„ํŠผ
if st.button("๐Ÿ”„ ๋‹ค์‹œ ํ’€๊ธฐ", key=f"reset_{i}"):
st.session_state[f"similar_question_answered_{i}"] = False
st.session_state[f"selected_answer_{i}"] = None
st.session_state[f"is_correct_{i}"] = None
st.rerun()
# ๋ฌธ์ œ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ
if st.button("โŒ ๋ฌธ์ œ ๋‹ซ๊ธฐ", key=f"close_{i}"):
st.session_state[f"show_similar_question_{i}"] = False
st.session_state[f"similar_question_answered_{i}"] = False
st.session_state[f"selected_answer_{i}"] = None
st.session_state[f"is_correct_{i}"] = None
st.rerun()
# ํ™”๋ฉด ์•„๋ž˜ ์—ฌ๋ฐฑ ์ถ”๊ฐ€
st.markdown("<br>" * 5, unsafe_allow_html=True) # 5์ค„์˜ ๋นˆ ์ค„ ์ถ”๊ฐ€
st.markdown("""
<div style="height: 100px;">
</div>
""", unsafe_allow_html=True) # ์ถ”๊ฐ€ ์—ฌ๋ฐฑ
else:
st.error("์œ ์‚ฌ ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
if st.button("โŒ ๋‹ซ๊ธฐ", key=f"close_error_{i}"):
st.session_state[f"show_similar_question_{i}"] = False
st.rerun()
# ํ™”๋ฉด ์•„๋ž˜ ์—ฌ๋ฐฑ ์ถ”๊ฐ€
st.markdown("<br>" * 5, unsafe_allow_html=True) # 5์ค„์˜ ๋นˆ ์ค„ ์ถ”๊ฐ€
st.markdown("""
<div style="height: 100px;">
</div>
""", unsafe_allow_html=True) # ์ถ”๊ฐ€ ์—ฌ๋ฐฑ
# # ํ‹€๋ฆฐ ๋ฌธ์ œ ๋ถ„์„
# if st.session_state.wrong_questions:
# st.write("### โœ๏ธ ํ‹€๋ฆฐ ๋ฌธ์ œ ๋ถ„์„")
# tabs = st.tabs([f"๐Ÿ“ ํ‹€๋ฆฐ ๋ฌธ์ œ #{i + 1}" for i in range(len(st.session_state.wrong_questions))])
# for i, (tab, (wrong_q, misconception_id)) in enumerate(zip(
# tabs,
# zip(st.session_state.wrong_questions, st.session_state.misconceptions)
# )):
# with tab:
# st.write("**๐Ÿ“‹ ๋ฌธ์ œ:**")
# st.write(wrong_q['QuestionText'])
# st.write("**โœ… ์ •๋‹ต:**", wrong_q['CorrectAnswer'])
# st.write("---")
# st.write("**๐Ÿ” ๊ด€๋ จ๋œ Misconception:**")
# if misconception_id and not pd.isna(misconception_id):
# misconception_text = misconception_model.misconception_names.get(misconception_id, "์ •๋ณด ์—†์Œ")
# st.info(f"Misconception ID: {int(misconception_id)}\n\n{misconception_text}")
# else:
# st.info("Misconception ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
if __name__ == "__main__":
main()
# random_state 42์—์„œ ์ •๋‹ต
# D C A A C
# A B B B B