Spaces:
Sleeping
Sleeping
import streamlit as st | |
import pandas as pd | |
import os | |
from src.SecondModule.module2 import SimilarQuestionGenerator | |
from src.ThirdModule.module3 import AnswerVerifier | |
import logging | |
from typing import Optional, Tuple | |
#from latex_formatter import LatexFormatter # LaTeX ํฌ๋งทํฐ import | |
from pylatexenc.latex2text import LatexNodes2Text | |
logging.basicConfig(level=logging.DEBUG) | |
# Streamlit ํ์ด์ง ๊ธฐ๋ณธ ์ค์ | |
st.set_page_config( | |
page_title="MisconcepTutor", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
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") | |
# ๋ฌธ์ ์์ฑ๊ธฐ ์ด๊ธฐํ | |
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 ๋ฐ์ดํฐ ๋ก๋ ํจ์ | |
def load_data(data_file = '/train.csv'): | |
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}") | |
return df | |
except FileNotFoundError: | |
st.error(f"ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {data_file}") | |
logger.error(f"File not found: {data_file}") | |
return None | |
def start_quiz(): | |
"""ํด์ฆ ์์ ๋ฐ ์ด๊ธฐํ""" | |
df = load_data() | |
if df is None or df.empty: | |
st.error("๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค. ๋ฐ์ดํฐ์ ์ ํ์ธํด์ฃผ์ธ์.") | |
return | |
st.session_state.questions = df.sample(n=10, random_state=42) | |
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 >= 10: | |
st.session_state.current_step = 'review' | |
# ์ ์ญ LaTeX ํฌ๋งทํฐ ์ธ์คํด์ค ์์ฑ | |
latex_formatter = LatexFormatter() | |
def display_math_content(content: str): | |
"""์ํ ๋ด์ฉ์ ํ๋ฉด์ ํ์""" | |
formatted_content = latex_formatter.format_expression(content) | |
st.latex(formatted_content) | |
st.markdown(formatted_content, unsafe_allow_html=True) | |
def format_answer_choice(choice: str) -> str: | |
"""์ ํ์ง LaTeX ํฌ๋งทํ """ | |
return latex_formatter.format_expression(choice) | |
def main(): | |
"""๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์ ๋ก์ง""" | |
st.title("MisconcepTutor") | |
# Generator ์ด๊ธฐํ | |
generator = load_question_generator() | |
# ์ด๊ธฐ ํ๋ฉด | |
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("---") | |
#display_math_question(current_q['QuestionText']) | |
display_math_content(current_q['QuestionText']) # display_math_question ๋์ display_math_content ์ฌ์ฉ | |
# ๋ณด๊ธฐ ํ์ | |
col1, col2 = st.columns(2) | |
with col1: | |
if st.button(f"A) {latex_formatter.format_expression(current_q['AnswerAText'])}", key="A"): | |
handle_answer('A', current_q) | |
st.rerun() | |
if st.button(f"C) {latex_formatter.format_expression(current_q['AnswerCText'])}", key="C"): | |
handle_answer('C', current_q) | |
st.rerun() | |
with col2: | |
if st.button(f"B) {latex_formatter.format_expression(current_q['AnswerBText'])}", key="B"): | |
handle_answer('B', current_q) | |
st.rerun() | |
if st.button(f"D) {latex_formatter.format_expression(current_q['AnswerDText'])}", key="D"): | |
handle_answer('D', current_q) | |
st.rerun() | |
# ๋ณต์ต ํ๋ฉด | |
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("**๐ ๋ฌธ์ :**") | |
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 = 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("### ๐ฏ ์ ์ฌ ๋ฌธ์ ") | |
#st.write(new_question['question']) | |
display_math_question(new_question['question']) | |
# ๋ต๋ณ ์ํ ํ์ธ | |
answered = st.session_state.get(f"similar_question_answered_{i}", False) | |
# ๋ณด๊ธฐ ํ์ | |
st.write("**๋ณด๊ธฐ:**") | |
col1, col2 = st.columns(2) | |
# # ๋ต๋ณํ์ง ์์ ๊ฒฝ์ฐ์๋ง ๋ฒํผ ํ์ฑํ | |
# if not answered: | |
# with col1: | |
# for option in ['A', 'C']: | |
# if st.button( | |
# f"{option}) {new_question['choices'][option]}", | |
# key=f"similar_{option}_{i}" | |
# ): | |
# st.session_state[f"similar_question_answered_{i}"] = True | |
# st.session_state[f"selected_answer_{i}"] = option | |
# correct_answer = st.session_state.get('current_similar_question_answer') | |
# if option == correct_answer: | |
# st.session_state[f"is_correct_{i}"] = True | |
# else: | |
# st.session_state[f"is_correct_{i}"] = False | |
# st.rerun() | |
# with col2: | |
# for option in ['B', 'D']: | |
# if st.button( | |
# f"{option}) {new_question['choices'][option]}", | |
# key=f"similar_{option}_{i}" | |
# ): | |
# st.session_state[f"similar_question_answered_{i}"] = True | |
# st.session_state[f"selected_answer_{i}"] = option | |
# correct_answer = st.session_state.get('current_similar_question_answer') | |
# if option == correct_answer: | |
# st.session_state[f"is_correct_{i}"] = True | |
# else: | |
# st.session_state[f"is_correct_{i}"] = False | |
# st.rerun() | |
# ๋ต๋ณํ์ง ์์ ๊ฒฝ์ฐ์๋ง ๋ฒํผ ํ์ฑํ | |
if not answered: | |
with col1: | |
for option in ['A', 'C']: | |
if st.button( | |
f"{option}) {latex_formatter.format_expression(new_question['choices'][option])}", | |
key=f"similar_{option}_{i}" | |
): | |
st.session_state[f"similar_question_answered_{i}"] = True | |
st.session_state[f"selected_answer_{i}"] = option | |
correct_answer = st.session_state.get('current_similar_question_answer') | |
if option == correct_answer: | |
st.session_state[f"is_correct_{i}"] = True | |
else: | |
st.session_state[f"is_correct_{i}"] = False | |
st.rerun() | |
with col2: | |
for option in ['B', 'D']: | |
if st.button( | |
f"{option}) {format_math_expression(new_question['choices'][option])}", | |
key=f"similar_{option}_{i}" | |
): | |
st.session_state[f"similar_question_answered_{i}"] = True | |
st.session_state[f"selected_answer_{i}"] = option | |
correct_answer = st.session_state.get('current_similar_question_answer') | |
if option == correct_answer: | |
st.session_state[f"is_correct_{i}"] = True | |
else: | |
st.session_state[f"is_correct_{i}"] = False | |
st.rerun() | |
# ๋ต๋ณํ ๊ฒฝ์ฐ ๊ฒฐ๊ณผ ํ์ | |
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 __name__ == "__main__": | |
main() | |
# random_state 42์์ ์ ๋ต | |
# D C A A C | |
# A B B B B | |