Quiz_App / src /streamlit_app.py
bharathmunakala's picture
Update src/streamlit_app.py
536daec verified
import streamlit as st
from educhain import Educhain, LLMConfig
from educhain.engines import qna_engine
from langchain_openai import ChatOpenAI
import os
import json
from datetime import datetime
import pandas as pd
import random
# Set page configuration at the very top of the script
st.set_page_config(page_title="Multilingual Quiz App", page_icon="🧠", layout="wide")
# Define supported languages
languages = [
"English", "Hindi", "Gujarati", "Bengali", "Tamil",
"Telugu", "Kannada", "Malayalam", "Punjabi", "Marathi",
"Urdu", "Assamese", "Odia", "Sanskrit", "Korean",
"Japanese", "Arabic", "French", "German", "Spanish",
"Portuguese", "Russian", "Chinese", "Vietnamese", "Thai",
"Indonesian", "Turkish", "Polish", "Ukrainian", "Dutch",
"Italian", "Greek", "Hebrew", "Persian", "Swedish",
"Norwegian", "Danish", "Finnish", "Czech", "Hungarian",
"Romanian", "Bulgarian", "Croatian", "Serbian", "Slovak",
"Slovenian", "Estonian", "Latvian", "Lithuanian", "Malay",
"Tagalog", "Swahili"
]
# Define question types
question_types = ["Multiple Choice", "True/False"]
# Define difficulty levels
difficulty_levels = ["Easy", "Medium", "Hard"]
# Initialize session state
if 'current_quiz' not in st.session_state:
st.session_state.current_quiz = None
if 'quiz_in_progress' not in st.session_state:
st.session_state.quiz_in_progress = False
if 'current_question' not in st.session_state:
st.session_state.current_question = 0
if 'user_answers' not in st.session_state:
st.session_state.user_answers = []
if 'user_score' not in st.session_state:
st.session_state.user_score = 0
if 'quiz_completed' not in st.session_state:
st.session_state.quiz_completed = False
if 'saved_quizzes' not in st.session_state:
st.session_state.saved_quizzes = []
if 'page' not in st.session_state:
st.session_state.page = "create" # Options: "create", "take", "history"
# --- Sidebar Navigation ---
with st.sidebar:
st.title("Create Multilingual Quiz")
# Navigation
st.subheader("📚 Navigation")
if st.button("Create Quiz", use_container_width=True):
st.session_state.page = "create"
if st.button("Saved Quizzes", use_container_width=True):
st.session_state.page = "saved"
if st.button("Quiz History", use_container_width=True):
st.session_state.page = "history"
st.header("⚙️ Configuration")
# API Key section
st.markdown("### API Key")
st.markdown("Get your free API key from [Sutra API](https://www.two.ai/sutra/api)")
api_key = st.text_input("Enter your Sutra API Key:", type="password")
st.markdown("---")
st.markdown("**Powered by** [Educhain](https://github.com/satvik314/educhain)")
st.markdown("**Using** [Sutra LLM](https://docs.two.ai/) for multilingual")
st.write("❤️ Built with [Streamlit](https://streamlit.io)")
# --- Initialize Educhain with Sutra Model ---
@st.cache_resource
def initialize_educhain(api_key):
if not api_key:
return None # Return None if API key is missing
sutra_model = ChatOpenAI(
api_key=api_key,
base_url="https://api.two.ai/v2",
model="sutra-v2",
temperature=0.9
)
llm_config = LLMConfig(custom_model=sutra_model)
return Educhain(llm_config)
# --- Utility Function to Convert Questions to Quiz Format ---
def convert_to_quiz_format(questions_obj, topic, language, difficulty):
quiz = {
"title": f"{topic} Quiz ({difficulty})",
"language": language,
"difficulty": difficulty,
"topic": topic,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"questions": []
}
if hasattr(questions_obj, "questions"):
for q in questions_obj.questions:
question_data = {
"question": q.question,
"answer": q.answer
}
if hasattr(q, 'options'):
question_data["type"] = "multiple_choice"
question_data["options"] = q.options
else:
question_data["type"] = "true_false"
question_data["options"] = ["True", "False"]
if hasattr(q, 'explanation') and q.explanation:
question_data["explanation"] = q.explanation
quiz["questions"].append(question_data)
return quiz
# --- Save Quiz Function ---
def save_quiz(quiz):
# Create a unique ID for the quiz
quiz_id = f"{len(st.session_state.saved_quizzes) + 1}_{datetime.now().strftime('%Y%m%d%H%M%S')}"
quiz["id"] = quiz_id
# Add to saved quizzes
st.session_state.saved_quizzes.append(quiz)
# Also save to disk (optional)
try:
# Check if file exists and load existing data
if os.path.exists("saved_quizzes.json"):
with open("saved_quizzes.json", "r") as f:
existing_quizzes = json.load(f)
else:
existing_quizzes = []
# Append new quiz
existing_quizzes.append(quiz)
# Save updated list
with open("saved_quizzes.json", "w") as f:
json.dump(existing_quizzes, f)
except Exception as e:
st.warning(f"Could not save quiz to disk: {str(e)}")
return quiz_id
# --- Start Quiz Function ---
def start_quiz(quiz):
st.session_state.current_quiz = quiz
st.session_state.quiz_in_progress = True
st.session_state.current_question = 0
st.session_state.user_answers = [None] * len(quiz["questions"])
st.session_state.user_score = 0
st.session_state.quiz_completed = False
# --- Submit Answer Function ---
def submit_answer(answer_index):
current_q = st.session_state.current_question
st.session_state.user_answers[current_q] = answer_index
# Check if answer is correct
correct_answer = st.session_state.current_quiz["questions"][current_q]["answer"]
# For multiple choice, the answer might be the option text or the option index
if st.session_state.current_quiz["questions"][current_q]["type"] == "multiple_choice":
# Try to match by index first (if answer is A, B, C, D)
if correct_answer in ["A", "B", "C", "D"]:
correct_index = ord(correct_answer) - ord("A")
if answer_index == correct_index:
st.session_state.user_score += 1
# Otherwise, match by option text
else:
options = st.session_state.current_quiz["questions"][current_q]["options"]
if answer_index < len(options) and options[answer_index] == correct_answer:
st.session_state.user_score += 1
# For true/false
else:
options = ["True", "False"]
user_answer = options[answer_index]
# Convert both answers to lowercase strings for comparison
if str(user_answer).lower() == str(correct_answer).lower():
st.session_state.user_score += 1
# Move to next question or end quiz
if current_q < len(st.session_state.current_quiz["questions"]) - 1:
st.session_state.current_question += 1
else:
st.session_state.quiz_completed = True
# Save quiz results to history
save_quiz_result()
# --- Save Quiz Result Function ---
def save_quiz_result():
result = {
"quiz_title": st.session_state.current_quiz["title"],
"language": st.session_state.current_quiz["language"],
"topic": st.session_state.current_quiz["topic"],
"difficulty": st.session_state.current_quiz["difficulty"],
"score": st.session_state.user_score,
"total": len(st.session_state.current_quiz["questions"]),
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
# Initialize history if not exists
if 'quiz_history' not in st.session_state:
st.session_state.quiz_history = []
# Add to history
st.session_state.quiz_history.append(result)
# Also save to disk (optional)
try:
# Check if file exists and load existing data
if os.path.exists("quiz_history.json"):
with open("quiz_history.json", "r") as f:
existing_history = json.load(f)
else:
existing_history = []
# Append new result
existing_history.append(result)
# Save updated list
with open("quiz_history.json", "w") as f:
json.dump(existing_history, f)
except Exception as e:
st.warning(f"Could not save quiz history to disk: {str(e)}")
# --- Create Quiz Page ---
def show_create_quiz_page():
st.markdown(
f'<h1><img src="https://framerusercontent.com/images/9vH8BcjXKRcC5OrSfkohhSyDgX0.png" width="60"/> Multilingual Quiz App</h1>',
unsafe_allow_html=True
)
# --- Initialize Educhain client if API key is provided ---
if not api_key:
st.warning("Please enter your Sutra API Key in the sidebar to continue.")
return
educhain_client = initialize_educhain(api_key)
if not educhain_client:
st.error("Failed to initialize Educhain. Please check your Sutra API key.")
return
qna_engine = educhain_client.qna_engine
# Quiz configuration
col1, col2, col3 = st.columns(3)
with col1:
selected_language = st.selectbox("Language:", languages)
with col2:
selected_question_type = st.selectbox("Question Type:", question_types)
with col3:
selected_difficulty = st.selectbox("Difficulty:", difficulty_levels)
topic = st.text_input("Quiz Topic:", "General Knowledge")
num_questions = st.slider("Number of Questions", 3, 10, 5)
custom_instructions = st.text_area(
"Custom Instructions (optional):",
placeholder=f"e.g. 'Focus on {selected_difficulty.lower()} concepts for {topic}'",
height=100
)
# Add language instruction to custom instructions
language_custom_instructions = f"Generate all questions, options, answers and explanations in {selected_language} language. Make questions {selected_difficulty.lower()} difficulty. {custom_instructions}"
# Generate quiz button
if st.button("Generate Quiz"):
with st.spinner(f"Generating {num_questions} {selected_question_type.lower()} questions in {selected_language}..."):
try:
# Use the appropriate method based on question type
if selected_question_type == "Multiple Choice":
questions = qna_engine.generate_questions(
topic=topic,
num=num_questions,
question_type="Multiple Choice",
custom_instructions=language_custom_instructions,
difficulty=selected_difficulty.lower(),
language=selected_language
)
else: # True/False
questions = qna_engine.generate_questions(
topic=topic,
num=num_questions,
question_type="True/False",
custom_instructions=language_custom_instructions,
difficulty=selected_difficulty.lower(),
language=selected_language
)
if not questions or not hasattr(questions, "questions"):
st.error("Failed to generate questions. Please try again with different parameters.")
return
# Convert to quiz format and save
quiz = convert_to_quiz_format(questions, topic, selected_language, selected_difficulty)
quiz_id = save_quiz(quiz)
st.success(f"Quiz generated successfully! Quiz ID: {quiz_id}")
# Preview quiz
with st.expander("Preview Quiz"):
for i, q in enumerate(quiz["questions"]):
st.subheader(f"Question {i+1}: {q['question']}")
st.write("Options:")
for j, opt in enumerate(q["options"]):
st.write(f" {chr(65 + j)}. {opt}")
st.write(f"**Correct Answer:** {q['answer']}")
if "explanation" in q and q["explanation"]:
st.write(f"**Explanation:** {q['explanation']}")
st.markdown("---")
# Button to start quiz
if st.button("Start This Quiz"):
start_quiz(quiz)
st.session_state.page = "take"
st.rerun()
except Exception as e:
st.error(f"Error generating questions: {str(e)}")
st.error("Please try again with different parameters or check your API key.")
# --- Saved Quizzes Page ---
def show_saved_quizzes_page():
st.markdown(
f'<h1><img src="https://framerusercontent.com/images/9vH8BcjXKRcC5OrSfkohhSyDgX0.png" width="60"/> Saved Quizzes</h1>',
unsafe_allow_html=True
)
if not st.session_state.saved_quizzes:
st.info("No saved quizzes yet. Create one first!")
return
# Create a dataframe for better display
quiz_data = []
for quiz in st.session_state.saved_quizzes:
quiz_data.append({
"ID": quiz.get("id", "Unknown"),
"Title": quiz.get("title", "Untitled"),
"Topic": quiz.get("topic", "Unknown"),
"Language": quiz.get("language", "Unknown"),
"Difficulty": quiz.get("difficulty", "Unknown"),
"Questions": len(quiz.get("questions", [])),
"Created": quiz.get("created_at", "Unknown")
})
df = pd.DataFrame(quiz_data)
st.dataframe(df, use_container_width=True)
# Select quiz to take
selected_quiz_id = st.selectbox(
"Select a quiz to take:",
options=[quiz.get("id", "Unknown") for quiz in st.session_state.saved_quizzes],
format_func=lambda x: next((q["title"] for q in st.session_state.saved_quizzes if q.get("id") == x), x)
)
# Start selected quiz
if st.button("Start Selected Quiz"):
selected_quiz = next((q for q in st.session_state.saved_quizzes if q.get("id") == selected_quiz_id), None)
if selected_quiz:
start_quiz(selected_quiz)
st.session_state.page = "take"
st.rerun()
# Option to delete a quiz
if st.button("Delete Selected Quiz"):
st.session_state.saved_quizzes = [q for q in st.session_state.saved_quizzes if q.get("id") != selected_quiz_id]
st.success("Quiz deleted successfully!")
st.rerun()
# --- Take Quiz Page ---
def show_take_quiz_page():
if not st.session_state.quiz_in_progress or not st.session_state.current_quiz:
st.warning("No quiz is currently in progress.")
if st.button("Go to Saved Quizzes"):
st.session_state.page = "saved"
st.rerun()
return
quiz = st.session_state.current_quiz
# Display quiz header
st.markdown(
f'<h1>{quiz["title"]}</h1>',
unsafe_allow_html=True
)
st.write(f"Language: {quiz['language']} | Difficulty: {quiz['difficulty']} | Topic: {quiz['topic']}")
# If quiz is completed, show results
if st.session_state.quiz_completed:
st.balloons()
st.markdown(f"## Quiz Completed!")
st.markdown(f"### Your Score: {st.session_state.user_score}/{len(quiz['questions'])}")
# Calculate percentage
percentage = (st.session_state.user_score / len(quiz['questions'])) * 100
st.progress(percentage / 100)
# Different messages based on score
if percentage >= 80:
st.success("Excellent! You've mastered this topic!")
elif percentage >= 60:
st.info("Good job! You have a solid understanding of the material.")
else:
st.warning("You might want to review this topic again.")
# Show answers and explanations
with st.expander("Review Questions and Answers"):
for i, (question, user_answer) in enumerate(zip(quiz["questions"], st.session_state.user_answers)):
correct_answer = question["answer"]
is_correct = False
# Determine if the answer was correct
if question["type"] == "multiple_choice":
if correct_answer in ["A", "B", "C", "D"]:
correct_index = ord(correct_answer) - ord("A")
is_correct = (user_answer == correct_index)
else:
is_correct = (question["options"][user_answer] == correct_answer)
else: # true/false
options = ["True", "False"]
# Convert both answers to lowercase strings for comparison
is_correct = (str(options[user_answer]).lower() == str(correct_answer).lower())
# Display question and answer
st.markdown(f"**Question {i+1}:** {question['question']}")
if question["type"] == "multiple_choice":
st.write("Options:")
for j, opt in enumerate(question["options"]):
prefix = "✅ " if (is_correct and user_answer == j) else "❌ " if (not is_correct and user_answer == j) else ""
highlight = "**" if (correct_answer in ["A", "B", "C", "D"] and j == ord(correct_answer) - ord("A")) or \
(correct_answer not in ["A", "B", "C", "D"] and opt == correct_answer) else ""
st.write(f" {prefix}{chr(65 + j)}. {highlight}{opt}{highlight}")
else: # true/false
st.write("Options:")
for j, opt in enumerate(["True", "False"]):
prefix = "✅ " if (is_correct and user_answer == j) else "❌ " if (not is_correct and user_answer == j) else ""
highlight = "**" if str(opt).lower() == str(correct_answer).lower() else ""
st.write(f" {prefix}{opt} {highlight}")
if "explanation" in question and question["explanation"]:
st.write(f"**Explanation:** {question['explanation']}")
st.markdown("---")
# Button to go back to saved quizzes
if st.button("Choose Another Quiz"):
st.session_state.page = "saved"
st.rerun()
# Button to create a new quiz
if st.button("Create New Quiz"):
st.session_state.page = "create"
st.rerun()
# If quiz is in progress, show current question
else:
current_q_index = st.session_state.current_question
total_questions = len(quiz["questions"])
current_q = quiz["questions"][current_q_index]
# Progress bar
st.progress((current_q_index) / total_questions)
st.write(f"Question {current_q_index + 1} of {total_questions}")
# Display question
st.markdown(f"## {current_q['question']}")
# Display options based on question type
if current_q["type"] == "multiple_choice":
for i, option in enumerate(current_q["options"]):
if st.button(f"{chr(65 + i)}. {option}", key=f"opt_{i}"):
submit_answer(i)
st.rerun()
else: # true/false
col1, col2 = st.columns(2)
with col1:
if st.button("True", use_container_width=True):
submit_answer(0)
st.rerun()
with col2:
if st.button("False", use_container_width=True):
submit_answer(1)
st.rerun()
# Option to skip question
if st.button("Skip Question"):
submit_answer(random.randint(0, len(current_q["options"]) - 1)) # Submit random answer
st.rerun()
# --- Quiz History Page ---
def show_history_page():
st.markdown(
f'<h1><img src="https://framerusercontent.com/images/9vH8BcjXKRcC5OrSfkohhSyDgX0.png" width="60"/> Quiz History</h1>',
unsafe_allow_html=True
)
# Initialize quiz_history if not exists
if 'quiz_history' not in st.session_state:
st.session_state.quiz_history = []
# Try to load from disk
try:
if os.path.exists("quiz_history.json"):
with open("quiz_history.json", "r") as f:
st.session_state.quiz_history = json.load(f)
except Exception:
pass
if not st.session_state.quiz_history:
st.info("No quiz history yet. Take a quiz first!")
return
# Create a dataframe for better display
history_data = []
for result in st.session_state.quiz_history:
percentage = (result["score"] / result["total"]) * 100
history_data.append({
"Date": result["date"],
"Quiz": result["quiz_title"],
"Topic": result["topic"],
"Language": result["language"],
"Difficulty": result["difficulty"],
"Score": f"{result['score']}/{result['total']} ({percentage:.1f}%)"
})
# Sort by date, newest first
history_data.sort(key=lambda x: x["Date"], reverse=True)
df = pd.DataFrame(history_data)
st.dataframe(df, use_container_width=True)
# Some analytics
if len(history_data) > 1:
st.subheader("Your Progress")
# Calculate average score by topic
topic_data = {}
for result in st.session_state.quiz_history:
topic = result["topic"]
if topic not in topic_data:
topic_data[topic] = {"total": 0, "correct": 0, "count": 0}
topic_data[topic]["correct"] += result["score"]
topic_data[topic]["total"] += result["total"]
topic_data[topic]["count"] += 1
# Create chart data
chart_data = []
for topic, data in topic_data.items():
percentage = (data["correct"] / data["total"]) * 100
chart_data.append({
"Topic": topic,
"Percentage": percentage,
"Quizzes Taken": data["count"]
})
# Display chart
if chart_data:
chart_df = pd.DataFrame(chart_data)
st.bar_chart(chart_df.set_index("Topic")["Percentage"])
# Clear history button
if st.button("Clear History"):
st.session_state.quiz_history = []
# Also delete from disk
if os.path.exists("quiz_history.json"):
os.remove("quiz_history.json")
st.success("History cleared!")
st.rerun()
# --- Main App Logic ---
def main():
# Load saved quizzes from disk on startup
if 'saved_quizzes' not in st.session_state or not st.session_state.saved_quizzes:
try:
if os.path.exists("saved_quizzes.json"):
with open("saved_quizzes.json", "r") as f:
st.session_state.saved_quizzes = json.load(f)
except Exception:
pass
# Display the appropriate page
if st.session_state.page == "create":
show_create_quiz_page()
elif st.session_state.page == "saved":
show_saved_quizzes_page()
elif st.session_state.page == "take" and st.session_state.quiz_in_progress:
show_take_quiz_page()
elif st.session_state.page == "history":
show_history_page()
else:
show_create_quiz_page()
if __name__ == "__main__":
main()