|
import streamlit as st |
|
from langchain.prompts import PromptTemplate |
|
from langchain.chains import LLMChain |
|
from langchain_google_genai import ChatGoogleGenerativeAI |
|
import fitz |
|
import json |
|
import docx |
|
import os |
|
|
|
|
|
|
|
if "mcqs" not in st.session_state: |
|
st.session_state.mcqs = [] |
|
if "current_q" not in st.session_state: |
|
st.session_state.current_q = 0 |
|
if "user_answers" not in st.session_state: |
|
st.session_state.user_answers = {} |
|
if "quiz_finished" not in st.session_state: |
|
st.session_state.quiz_finished = False |
|
if "language" not in st.session_state: |
|
st.session_state.language = "English" |
|
|
|
|
|
st.sidebar.title("π Language / μΈμ΄") |
|
language = st.sidebar.selectbox( |
|
"Select Language / μΈμ΄ μ ν", |
|
["English", "νκ΅μ΄"], |
|
index=0 if st.session_state.language == "English" else 1 |
|
) |
|
st.session_state.language = language |
|
|
|
|
|
ui_text = { |
|
"English": { |
|
"title": "π PDF/Word based Self EXAM", |
|
"sidebar_title": "Upload & Settings", |
|
"upload_prompt": "Upload a file (PDF or Word)", |
|
"num_questions": "Number of questions", |
|
"generate_button": "Generate EXAMs", |
|
"no_file_error": "Please upload a file.", |
|
"generating": "Extracting text and generating EXAMs...", |
|
"success": "β
EXAMs generated successfully!", |
|
"error": "Error generating EXAMs:", |
|
"question_prefix": "Question", |
|
"choose_answer": "Choose an answer:", |
|
"next_button": "Next", |
|
"quiz_completed": "π Quiz completed!", |
|
"results_header": "π Quiz Results", |
|
"your_answer": "Your answer:", |
|
"correct_answer": "Correct answer:", |
|
"score": "β
You scored {score} out of {total}" |
|
}, |
|
"νκ΅μ΄": { |
|
"title": "π PDF/Word κΈ°λ° κ°κ΄μ λ¬Έμ μμ±κΈ°", |
|
"sidebar_title": "νμΌ μ
λ‘λ λ° μ€μ ", |
|
"upload_prompt": "νμΌ μ
λ‘λ (PDF λλ Word)", |
|
"num_questions": "λ¬Έμ κ°μ", |
|
"generate_button": "λ¬Έμ μμ±", |
|
"no_file_error": "νμΌμ μ
λ‘λν΄μ£ΌμΈμ.", |
|
"generating": "ν
μ€νΈ μΆμΆ λ° λ¬Έμ μμ± μ€...", |
|
"success": "β
λ¬Έμ κ° μ±κ³΅μ μΌλ‘ μμ±λμμ΅λλ€!", |
|
"error": "λ¬Έμ μμ± μ€λ₯:", |
|
"question_prefix": "λ¬Έμ ", |
|
"choose_answer": "λ΅μ μ ννμΈμ:", |
|
"next_button": "λ€μ", |
|
"quiz_completed": "π ν΄μ¦κ° μλ£λμμ΅λλ€!", |
|
"results_header": "π ν΄μ¦ κ²°κ³Ό", |
|
"your_answer": "λΉμ μ λ΅:", |
|
"correct_answer": "μ λ΅:", |
|
"score": "β
{total}λ¬Έμ μ€ {score}λ¬Έμ λ₯Ό λ§μΆμ
¨μ΅λλ€" |
|
} |
|
} |
|
|
|
|
|
texts = ui_text[language] |
|
|
|
|
|
st.title(texts["title"]) |
|
|
|
|
|
st.sidebar.title(texts["sidebar_title"]) |
|
|
|
|
|
uploaded_file = st.sidebar.file_uploader(texts["upload_prompt"], type=["pdf", "docx"]) |
|
|
|
|
|
number_of_questions = st.sidebar.slider(texts["num_questions"], min_value=1, max_value=20, value=5) |
|
|
|
|
|
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") |
|
llm = ChatGoogleGenerativeAI( |
|
model="gemini-2.0-flash-exp", |
|
google_api_key=GOOGLE_API_KEY, |
|
temperature=0.7 |
|
) |
|
|
|
|
|
template_english = ("You are an expert EXAM generator. Generate {number} unique multiple-choice questions from the given text.\n" |
|
"Each question must have exactly 1 correct answer and 3 incorrect options.\n" |
|
"Strictly return output in the following JSON format (no explanations, no markdown):\n" |
|
"[\n" |
|
" {{\n" |
|
' "question": "What is ...?",\n' |
|
' "options": ["Option A", "Option B", "Option C", "Option D"],\n' |
|
' "answer": "Option D"\n' |
|
" }},\n" |
|
" ...\n" |
|
"]\n" |
|
"TEXT:\n" |
|
"{text}") |
|
|
|
template_korean = ("λΉμ μ μ λ¬Έ κ°κ΄μ λ¬Έμ μΆμ μμ
λλ€. μ£Όμ΄μ§ ν
μ€νΈμμ {number}κ°μ κ³ μ ν κ°κ΄μ λ¬Έμ λ₯Ό μμ±νμΈμ.\n" |
|
"κ° λ¬Έμ λ μ νν 1κ°μ μ λ΅κ³Ό 3κ°μ μ€λ΅μ κ°μ ΈμΌ ν©λλ€.\n" |
|
"λ€μ JSON νμμΌλ‘λ§ μΆλ ₯νμΈμ (μ€λͺ
μ΄λ λ§ν¬λ€μ΄ μμ΄):\n" |
|
"[\n" |
|
" {{\n" |
|
' "question": "...λ 무μμ
λκΉ?",\n' |
|
' "options": ["μ νμ§ A", "μ νμ§ B", "μ νμ§ C", "μ νμ§ D"],\n' |
|
' "answer": "μ νμ§ D"\n' |
|
" }},\n" |
|
" ...\n" |
|
"]\n" |
|
"ν
μ€νΈ:\n" |
|
"{text}") |
|
|
|
|
|
template = template_english if language == "English" else template_korean |
|
|
|
prompt = PromptTemplate( |
|
input_variables=["text", "number"], |
|
template=template |
|
) |
|
|
|
mcq_chain = LLMChain(llm=llm, prompt=prompt) |
|
|
|
|
|
def extract_text(file): |
|
if file.name.endswith(".pdf"): |
|
|
|
file_bytes = file.read() |
|
|
|
doc = fitz.open(stream=file_bytes, filetype="pdf") |
|
|
|
text = "" |
|
for page in doc: |
|
text += page.get_text() |
|
return text |
|
elif file.name.endswith(".docx"): |
|
doc = docx.Document(file) |
|
return "\n".join([para.text for para in doc.paragraphs]) |
|
return "" |
|
|
|
|
|
if st.sidebar.button(texts["generate_button"]): |
|
if uploaded_file is None: |
|
st.error(texts["no_file_error"]) |
|
else: |
|
with st.spinner(texts["generating"]): |
|
text = extract_text(uploaded_file) |
|
try: |
|
response = mcq_chain.run(text=text, number=str(number_of_questions)) |
|
|
|
response = response.strip() |
|
if response.startswith("```json"): |
|
response = response[7:] |
|
if response.endswith("```"): |
|
response = response[:-3] |
|
mcqs_json = json.loads(response) |
|
st.session_state.mcqs = mcqs_json |
|
st.session_state.current_q = 0 |
|
st.session_state.user_answers = {} |
|
st.session_state.quiz_finished = False |
|
st.success(texts["success"]) |
|
except Exception as e: |
|
st.error(f"{texts['error']} {e}") |
|
|
|
|
|
if st.session_state.mcqs and not st.session_state.quiz_finished: |
|
idx = st.session_state.current_q |
|
q_data = st.session_state.mcqs[idx] |
|
|
|
st.subheader(f"{texts['question_prefix']} {idx + 1}: {q_data['question']}") |
|
|
|
with st.form(key=f"form_{idx}"): |
|
selected_option = st.radio(texts["choose_answer"], q_data["options"], key=f"radio_{idx}") |
|
submitted = st.form_submit_button(texts["next_button"]) |
|
|
|
if submitted: |
|
st.session_state.user_answers[idx] = selected_option |
|
if idx < len(st.session_state.mcqs) - 1: |
|
st.session_state.current_q += 1 |
|
st.rerun() |
|
else: |
|
st.session_state.quiz_finished = True |
|
st.success(texts["quiz_completed"]) |
|
st.rerun() |
|
|
|
|
|
if st.session_state.quiz_finished: |
|
st.header(texts["results_header"]) |
|
score = 0 |
|
total = len(st.session_state.mcqs) |
|
|
|
for i, q in enumerate(st.session_state.mcqs): |
|
user_ans = st.session_state.user_answers.get(i) |
|
correct_ans = q["answer"] |
|
if user_ans == correct_ans: |
|
score += 1 |
|
|
|
|
|
st.markdown(f"**{texts['question_prefix']}{i+1}: {q['question']}**") |
|
|
|
|
|
if user_ans == correct_ans: |
|
st.markdown(f"- {texts['your_answer']} :green[{user_ans}] β") |
|
else: |
|
st.markdown(f"- {texts['your_answer']} :red[{user_ans}] β") |
|
st.markdown(f"- {texts['correct_answer']} :green[{correct_ans}]") |
|
st.markdown("---") |
|
|
|
|
|
score_text = texts["score"].format(score=score, total=total) |
|
st.success(score_text) |
|
|
|
|
|
if language == "English": |
|
reset_text = "Start New Quiz" |
|
else: |
|
reset_text = "μ ν΄μ¦ μμ" |
|
|
|
if st.button(reset_text): |
|
st.session_state.mcqs = [] |
|
st.session_state.current_q = 0 |
|
st.session_state.user_answers = {} |
|
st.session_state.quiz_finished = False |
|
st.rerun() |