import streamlit as st from grammarcorrector import GrammarCorrector import errant import spacy import re import string import json import streamlit.components.v1 as components import random import requests import subprocess import os import math from streamlit_lottie import st_lottie import plotly.express as px from sqlalchemy.ext.mutable import MutableList, MutableDict from collections import defaultdict from sqlalchemy.exc import OperationalError import pandas as pd from collections import defaultdict import time import datetime # Removed MySQL-specific imports: # import mysql.connector # from mysql.connector import Error from sqlalchemy import create_engine, Column, Integer, String, Boolean, ForeignKey, JSON, Time, DateTime from sqlalchemy.orm import sessionmaker, declarative_base # Set page configuration at the top st.set_page_config(page_title="GrammarTool", page_icon="✏️") @st.cache_resource def get_spacy_nlp(): try: return spacy.load("en_core_web_sm") except OSError: # Download the model if it's not available subprocess.run(["python", "-m", "spacy", "download", "en_core_web_sm"]) return spacy.load("en_core_web_sm") @st.cache_resource def get_errant_annotator(): return errant.load('en') @st.cache_resource def get_grammar_corrector(): return GrammarCorrector(device="cpu") annotator = get_errant_annotator() grammar = get_grammar_corrector() nlp = get_spacy_nlp() # Load the error codes mapping file_path = os.path.join(os.path.dirname(__file__), 'data/error_code.json') with open(file_path, 'r') as f: error_code_data = json.load(f) @st.cache_data def load_tasks(): file_path = os.path.join(os.path.dirname(__file__), 'data/tasks.json') if not os.path.exists(file_path): st.error(f"Tasks file not found: {file_path}") return {} with open(file_path, 'r', encoding='utf-8') as f: try: tasks_dict = json.load(f) if not tasks_dict: st.error("Tasks file is empty or incorrectly formatted!") return tasks_dict except json.JSONDecodeError: st.error("Error loading tasks.json! Check if the JSON is valid.") return {} @st.cache_data def load_rules(): file_path = os.path.join(os.path.dirname(__file__), 'data/rules.json') with open(file_path, 'r', encoding='utf-8') as f: rules_dict = json.load(f) return rules_dict # Create a dictionary mapping error_code to full_error_name and explanation error_code_mapping = { item['error_code']: { 'full_error_name': item['full_error_name'], 'explanation': item['explanation'], 'css_structure': item['css_structure'] } for item in error_code_data } def load_questions(tutorial_num_list): """Load the JSON and filter tasks by tutorial_num_list (a list of tutorial IDs).""" script_dir = os.path.dirname(os.path.abspath(__file__)) file_path = os.path.join(script_dir, 'data/practice.json') with open(file_path, 'r') as file: tasks = json.load(file) filtered = [task for task in tasks if any(tnum in task["Tutorial number"] for tnum in tutorial_num_list)] return filtered def get_questions_for_tutorial(tasks, num_questions=8): """ Selects a random subset of questions from the loaded tasks. """ questions = tasks.get("Questions", []) if not questions: st.warning("No questions available for this tutorial.") return [] if len(questions) < num_questions: st.info(f"Only {len(questions)} questions available for this tutorial.") return questions else: return random.sample(questions, num_questions) # --- SQLite Connection Setup --- # Instead of connecting to a remote MySQL database, we create a local SQLite database. # The database file (grammar_tool_db.sqlite) will be created in your working directory. engine = create_engine("sqlite:///grammar_tool_db.sqlite", connect_args={"check_same_thread": False}) # Create the session Session = sessionmaker(bind=engine) session = Session() Base = declarative_base() class Student(Base): __tablename__ = 'students' id = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(100), unique=True, nullable=False) email = Column(String(100), unique=True, nullable=False) password = Column(String(255), nullable=False) participate = Column(Boolean, nullable=False) age = Column(Integer) country = Column(String(100)) language = Column(String(45)) main_goal = Column(String(45)) field = Column(String(1000)) preferences = Column(String(100)) proficiency = Column(String(45)) tasks_to_do = Column(JSON) adapt_logs = Column(JSON) mode_1 = Column(String(45)) mode_2 = Column(String(45)) mode_3 = Column(String(45)) class SelfAssessment(Base): __tablename__ = 'self_assessment' student_id = Column(Integer, ForeignKey('students.id'), primary_key=True) skill = Column(String(255), primary_key=True) score = Column(Integer, nullable=False) class TasksDone(Base): __tablename__ = "tasks_done" id = Column(Integer, primary_key=True, autoincrement=True) student_id = Column(Integer, ForeignKey("students.id")) task_id = Column(String(50)) date = Column(DateTime, nullable=True) time = Column(Time, nullable=True) errors = Column(JSON, nullable=True) complexity = Column(JSON, nullable=True) fluency = Column(JSON, nullable=True) processed_errors = Column(JSON, nullable=True) full_text = Column(JSON, nullable=True) class Errors(Base): __tablename__ = "errors" id = Column(Integer, primary_key=True, autoincrement=True) student_id = Column(Integer, ForeignKey("students.id"), nullable=False) gram_err = Column(MutableDict.as_mutable(JSON), nullable=True) spell_err = Column(MutableList.as_mutable(JSON), nullable=True) to_do = Column(MutableList.as_mutable(JSON), nullable=True) done = Column(MutableDict.as_mutable(JSON), nullable=True) # Create all tables (this will create the SQLite database file if it doesn't exist) Base.metadata.create_all(engine) def update_student_mode(student_id, mode_number): """ Update mode_1/mode_2/mode_3 column to 'yes' for the given student. """ student = session.query(Student).filter_by(id=student_id).first() if not student: st.error("User not found in the database.") return if mode_number == 1: student.mode_1 = "yes" elif mode_number == 2: student.mode_2 = "yes" elif mode_number == 3: student.mode_3 = "yes" session.commit() def navigate_to_mode(): st.session_state.page = 'mode_page' def navigate_to_personal_info(): st.session_state.page ='personal_info' def navigate_to_proficiency(): st.session_state.page = 'proficiency' def navigate_to_self_assessment(): st.session_state.page = 'self_assessment' def navigate_to_dashboard(): st.session_state.page ='user_dashboard' def navigate_to_mode_1(): st.session_state.page ='mode_1_page' def navigate_to_spelling(): st.session_state.page ='spelling_practice' def mode_page(): st.title("Choose the Mode") mode_options = { "Adaptive test": 3, # Mode 3 "Mode 1": 1, # Mode 1 "Mode 2 (personalized)": 2 # Mode 2 } selected_mode = st.radio( label="Please select a mode:", options=list(mode_options.keys()), index=0, key="selected_mode" ) if st.button("Next"): mode_number = mode_options.get(selected_mode, None) user_info = st.session_state.get("user_info") if not user_info or not hasattr(user_info, "id"): st.error("No logged-in user found. Please log in first.") return try: update_student_mode(user_info.id, mode_number) except Exception as e: st.error(f"Failed to update mode in DB: {e}") return if mode_number == 3: navigate_to_proficiency() st.rerun() elif mode_number == 1: navigate_to_mode_1() st.rerun() elif mode_number == 2: if not is_personal_info_complete(user_info): st.warning("Redirecting you to Personal Info page...") navigate_to_personal_info() st.rerun() if not is_self_assessment_complete(user_info.id): st.warning("Redirecting you to Self-Assessment page...") navigate_to_self_assessment() st.rerun() if not is_proficiency_complete(user_info): st.warning("Redirecting you to Proficiency Test page...") navigate_to_proficiency() st.rerun() st.success("All steps completed. Redirecting you to the personalized dashboard...") navigate_to_dashboard() st.rerun() st.rerun() def preprocess_error_types(session): tasks = session.query(TasksDone).all() for task in tasks: errors = task.errors if not errors or not errors.get("error_types"): continue processed = [] for e in errors["error_types"]: if isinstance(e, str): processed.append({"error_type": e}) elif isinstance(e, dict): processed.append(e) task.processed_errors = processed session.commit() def update_errors_table_with_processed(session): MAX_RETRIES = 3 RETRY_DELAY = 2 # seconds retries = 0 while retries < MAX_RETRIES: try: with session.no_autoflush: tasks = session.query(TasksDone).filter(TasksDone.processed_errors.isnot(None)).all() if not tasks: return tasks_by_student = defaultdict(list) for task in tasks: tasks_by_student[task.student_id].append(task) for student_id, task_list in tasks_by_student.items(): errors_obj = session.query(Errors).filter_by(student_id=student_id).one_or_none() if not errors_obj: errors_obj = Errors(student_id=student_id, gram_err={}, spell_err=[], to_do=[]) session.add(errors_obj) session.flush() for task in task_list: for e in task.processed_errors: etype = e.get("error_type") if not etype or etype == "OTHER": continue if etype == "SPELL": corr_str = e.get("corrected_string") if corr_str and corr_str not in errors_obj.spell_err: errors_obj.spell_err.append(corr_str) else: if not errors_obj.gram_err: errors_obj.gram_err = {} errors_obj.gram_err[etype] = errors_obj.gram_err.get(etype, 0) + 1 if errors_obj.gram_err: max_count = max(errors_obj.gram_err.values()) errors_obj.to_do = [k for k, v in errors_obj.gram_err.items() if v == max_count] else: errors_obj.to_do = [] session.commit() return except OperationalError as e: session.rollback() retries += 1 if retries < MAX_RETRIES: time.sleep(RETRY_DELAY) else: print(f"Database lock error: {e}, retries exhausted.") raise def insert_student_data( username, email, password, participate, age=None, country=None, language=None, main_goal=None, field=None, preferences=None, proficiency=None ): preferences_clean = remove_emojis(preferences) if preferences else None student = session.query(Student).filter_by(email=email).first() if not student: student = Student( username=username, email=email, password=password, participate=participate, age=age, country=country, language=language, main_goal=main_goal, field=field, preferences=preferences_clean, proficiency=proficiency ) session.add(student) else: student.username = username student.password = password student.participate = participate student.age = age student.country = country student.language = language student.main_goal = main_goal student.field = field student.preferences = preferences_clean student.proficiency = proficiency session.commit() def fetch_student_data(username): return session.query(Student).filter_by(username=username).first() def insert_self_assessment_data(student_id, skill, score): assessment = SelfAssessment( student_id=student_id, skill=skill, score=score ) session.add(assessment) session.commit() def fetch_self_assessments(student_id): return session.query(SelfAssessment).filter_by(student_id=student_id).all() def handle_login(username, password): user = fetch_student_data(username) if user and password == user.password: st.session_state.user_info = user st.session_state.student_id = user.id return True else: st.error("Invalid username or password.") return False def handle_signup(username, email, password, participate_in_research): insert_student_data( username=username, email=email, password=password, participate=participate_in_research ) user = fetch_student_data(username) if user: st.session_state.user_info = user st.session_state.page = "personal_info" else: st.error("Signup failed. Please try again.") def handle_forgot_password(email): user = session.query(Student).filter_by(email=email).first() if user: st.success(f"A password reset link has been sent to {email}.") else: st.error("No account found with that email.") def remove_emojis(text): if not text: return text emoji_pattern = re.compile( "[" "\U0001F600-\U0001F64F" # emoticons "\U0001F300-\U0001F5FF" # symbols & pictographs "\U0001F680-\U0001F6FF" # transport & map symbols "\U0001F1E0-\U0001F1FF" # flags (iOS) "\U00002500-\U00002BEF" # various symbols "\U00002702-\U000027B0" # dingbats "\U0001F900-\U0001F9FF" # supplemental symbols "\U0001FA70-\U0001FAFF" # symbols extended-A "\U00002600-\U000026FF" # miscellaneous symbols "]+", flags=re.UNICODE ) return emoji_pattern.sub(r'', text).strip() def personal_info_page(): render_progress_bar() st.title("πŸ‘€ Personal Information") age_text = st.text_input("Age:", value="") age = int(age_text) if age_text.isdigit() else None country = st.text_input("Country:") native_language = st.text_input("Native Language:") main_goal = st.selectbox("🎯 Main Goal:", ["", "Work", "Study", "General Communication"]) field_value = None if main_goal == "Work": work_options = st.selectbox( "Select Work Option:", ["", "Healthcare 🩺", "Engineering πŸ› οΈ", "Business πŸ“ˆ", "Education πŸ“š"] ) field_value = remove_emojis(work_options) if work_options else None elif main_goal == "Study": study_options = st.selectbox( "Select Study Option:", ["", "High school", "Bachelor's degree", "Master's degree", "Doctorate/PhD"] ) if study_options in ["Bachelor's degree", "Master's degree", "Doctorate/PhD"]: major_options = st.selectbox( "Select Your Major:", ["", "Medicine 🩺", "Science and Technology πŸ”¬", "Business and Finance 🏦", "Arts and HumanitiesπŸ“–"] ) if major_options: study_combined = f"{study_options}, Major: {remove_emojis(major_options)}" else: study_combined = study_options else: study_combined = study_options field_value = remove_emojis(study_combined) if study_combined else None interests = st.multiselect("Interests:", ["Travel ✈️", "Sports ⚽️", "Movies 🎬"]) stripped_interests = [remove_emojis(i) for i in interests] preferences_value = ", ".join(stripped_interests) if stripped_interests else None if st.button("Continue"): user_info = st.session_state.user_info if user_info and hasattr(user_info, 'id'): insert_student_data( username=user_info.username, email=user_info.email, password=user_info.password, participate=user_info.participate, age=age, country=country, language=native_language, main_goal=main_goal, field=field_value, preferences=preferences_value ) st.success("Information saved successfully.") navigate_to_self_assessment() st.rerun() else: st.error("Error: User information is missing.") # The rest of your functions (e.g., load_item_bank, adaptive test functions, etc.) # remain unchanged. def load_item_bank(): file_path = 'data/item_bank.json' with open(file_path, "r", encoding="utf-8") as f: data = json.load(f) for item in data: if "id" not in item or "level" not in item or "question" not in item or "answer" not in item: print(f"Missing keys in item: {item}") if item["level"] not in ["easy", "medium", "high"]: print(f"Invalid level in item: {item}") item["level"] = "medium" random.shuffle(data) return data def prob_correct(item_level, participant_level): item_level = int(item_level) participant_level = int(participant_level) if participant_level > item_level: return 0.95 elif participant_level == item_level: return 0.6 else: return 0.25 def calculate_entropy(belief): return -sum(prob * math.log2(prob) for prob in belief.values() if prob > 0) def expected_entropy(question, belief, item_level, level_map): prob_correct_ans = sum(prob_correct(item_level, level_map[level]) * belief[level] for level in belief) prob_incorrect_ans = 1 - prob_correct_ans belief_correct = {lvl: belief[lvl] * prob_correct(item_level, level_map[lvl]) for lvl in belief} belief_incorrect = {lvl: belief[lvl] * (1 - prob_correct(item_level, level_map[lvl])) for lvl in belief} total_correct = sum(belief_correct.values()) total_incorrect = sum(belief_incorrect.values()) for lvl in belief_correct: belief_correct[lvl] /= total_correct or 1 belief_incorrect[lvl] /= total_incorrect or 1 entropy_correct = calculate_entropy(belief_correct) entropy_incorrect = calculate_entropy(belief_incorrect) return prob_correct_ans * entropy_correct + prob_incorrect_ans * entropy_incorrect def select_question(belief, item_bank, asked_questions, level_map): min_entropy = float("inf") best_question = None for item in item_bank: if item["id"] not in asked_questions: item_level = level_map[item["level"]] exp_entropy = expected_entropy(item, belief, item_level, level_map) if exp_entropy < min_entropy: min_entropy = exp_entropy best_question = item return best_question def update_belief(belief, item_level, correct, level_map): for lvl in belief: likelihood = prob_correct(item_level, level_map[lvl]) if correct else (1 - prob_correct(item_level, level_map[lvl])) belief[lvl] *= likelihood total = sum(belief.values()) for lvl in belief: belief[lvl] /= total or 1 def stop_criterion(belief, threshold=0.9, min_questions=10, total_questions=0, max_questions=30): if total_questions < min_questions: return False if max(belief.values()) >= threshold: return True if total_questions >= max_questions: return True return False def go_next(): st.session_state.current_question = None st.session_state.submitted_answer = None st.session_state.phase = "answering" if stop_criterion( st.session_state.belief, min_questions=10, threshold=0.9, total_questions=st.session_state.total_questions, max_questions=30, ): st.session_state.completed = True import streamlit as st import time import datetime # ... your other imports ... def proficiency_page(): st.title("Adaptive Grammar Test") # Check if user is logged in user_info = st.session_state.get("user_info") if not user_info or not hasattr(user_info, "id"): st.error("User ID is missing. Please log in to take the test.") return # Detect the mode user came from (saved in session_state) mode_number = st.session_state.get("mode_number", None) # Retrieve the student ID from user_info student_id = user_info.id # Load item bank item_bank = load_item_bank() # Levels must match what's in your item bank levels = ["pre", "easy", "medium", "high", "post"] level_map = {level: i for i, level in enumerate(levels)} # ------------------------- # 1) INIT SESSION STATE # ------------------------- if "belief" not in st.session_state: st.session_state.belief = {level: 1 / len(levels) for level in levels} st.session_state.asked_questions = set() st.session_state.current_question = None st.session_state.submitted_answer = None st.session_state.total_questions = 0 st.session_state.history = [] st.session_state.completed = False st.session_state.question_number = 0 # ---------------------------- # 2) IF TEST IS ALREADY COMPLETE # ---------------------------- if st.session_state.completed: st.success("Test Completed!") final_level = max(st.session_state.belief, key=st.session_state.belief.get) st.write(f"Your estimated proficiency level: **{final_level.capitalize()}**") # Save to DB student = session.query(Student).filter_by(id=student_id).first() if student: student.adapt_logs = st.session_state.history student.proficiency = final_level # 3) Update tasks_to_do according to final_level tasks_list = TASKS_TO_DATABASE.get(final_level.lower(), []) student.tasks_to_do = json.dumps(tasks_list) session.commit() session.commit() st.success("Your adaptive test logs and final proficiency level have been saved to the database.") if st.button("Next"): # If they came from mode 2, proceed to personal dashboard navigate_to_dashboard() # or navigate_to_personal_dashboard() st.rerun() else: st.error(f"No student found in DB with id = {student_id}. Cannot save test results.") return # Exit function to prevent further questions from being displayed # ------------------------- # 3) LOAD A NEW QUESTION # ------------------------- if st.session_state.current_question is None: st.session_state.total_questions += 1 st.session_state.question_number = st.session_state.total_questions next_q = select_question( st.session_state.belief, item_bank, st.session_state.asked_questions, level_map ) # If no question returned, mark test as completed if not next_q: st.session_state.completed = True st.rerun() return # Save the newly selected question st.session_state.current_question = next_q st.session_state.submitted_answer = None question = st.session_state.current_question # ------------------------- # 4) DISPLAY THE QUESTION # ------------------------- st.write(f"**Question {st.session_state.question_number}:** {question['question']}") # Let the user pick an answer question_key = f"selected_answer_{question['id']}" selected_answer = st.radio("", question["options"], key=question_key, index=None) # One combined button to submit answer & go to next if st.button("Submit & Next"): if selected_answer is None: st.warning("Please select an answer before submitting.") st.stop() # Stop execution here so user can pick an answer. correct = (selected_answer == question["answer"]) before_belief = st.session_state.belief.copy() update_belief( st.session_state.belief, level_map[question["level"]], correct, level_map ) after_belief = st.session_state.belief.copy() # Record history st.session_state.history.append({ "question_id": question["id"], "level": question["level"], "question": question["question"], "user_answer": selected_answer, "correct": correct, "belief_before": before_belief, "belief_after": after_belief, "current_number": st.session_state.question_number, }) # Mark this question as asked st.session_state.asked_questions.add(question["id"]) # Check stopping criteria if stop_criterion( st.session_state.belief, min_questions=10, threshold=0.9, total_questions=st.session_state.total_questions, max_questions=30, ): st.session_state.completed = True st.rerun() return # Otherwise, clear so we load the next question st.session_state.current_question = None st.session_state.submitted_answer = None st.rerun() def fetch_tasks(student_id): """ Fetch the tasks assigned to a student. """ student = session.query(Student).filter_by(id=student_id).first() if student and student.tasks_to_do: return json.loads(student.tasks_to_do) # Convert JSON string back to list return [] def self_assessment_page(): render_progress_bar() st.title("Self-Assessment") st.header("Rate your difficulty for each skill (1 = very easy, 10 = very difficult)") skills = { 'Word Order': 'slider_word_order', 'Tenses': 'slider_tenses', 'Complex Sentence': 'slider_complex_sentence', 'Punctuation': 'slider_punctuation', 'Prepositions': 'slider_prepositions', 'Spelling': 'slider_spelling' } skill_scores = {} for skill, key in skills.items(): if key not in st.session_state: st.session_state[key] = 1 skill_scores[key] = st.slider(skill, 1, 10, key=key, value=st.session_state[key]) if st.button("Submit Assessment"): user_info = st.session_state.user_info if user_info and hasattr(user_info, 'id'): for skill, key in skills.items(): score = skill_scores[key] insert_self_assessment_data( student_id=user_info.id, skill=skill, score=score ) st.success("Self-assessment submitted!") navigate_to_proficiency() st.rerun() else: st.error("Error: User information is missing.") def is_personal_info_complete(student: Student) -> bool: """ Decide what 'complete' means for personal info. For example, check if age, country, language are set. Adjust the logic to your actual definition of 'complete'. """ # Example: If any of these are None, personal info isn't complete if not student.age or not student.country or not student.language: return False return True def is_self_assessment_complete(student_id: int) -> bool: """ Check if the user has at least one entry in the self_assessment table. Or you could check for a minimum set of skill entries, etc. """ entries = session.query(SelfAssessment).filter_by(student_id=student_id).all() return len(entries) > 0 def is_proficiency_complete(student: Student) -> bool: """ For proficiency, assume it's 'complete' if student.proficiency is not None or empty. Adjust as needed. """ return bool(student.proficiency) # True if student.proficiency is non-empty def login_registration_page(): render_progress_bar() st.title("Login / Sign Up") tabs = st.tabs(["Login", "Sign Up"]) # Login Tab with tabs[0]: st.header("Welcome Back!") username = st.text_input("πŸ‘€ Username:", placeholder="Your username", key="login_username_input_unique_1") password = st.text_input("πŸ”’ Password:", type="password", placeholder="Your password", key="login_password_input_unique_1") if st.button("πŸš€ Log In", key="login_button_unique_1"): if handle_login(username, password): # Validate login against the `students` table student = fetch_student_data(username) if student: st.session_state.user_info = student st.success("Logged in successfully!") navigate_to_mode() st.rerun() else: st.error("Failed to fetch user data. Please try again.") else: st.error("Invalid username or password.") # Sign-Up Tab with tabs[1]: st.header("Create a New Account") new_username = st.text_input("πŸ‘€ Choose a Username:", placeholder="Pick a unique username", key="signup_username_input_unique_1") email = st.text_input("βœ‰οΈ Email:", placeholder="Your email address", key="signup_email_input_unique_1") new_password = st.text_input("πŸ”’ Set a Password:", type="password", placeholder="Choose a strong password", key="signup_password_input_unique_1") participate = st.checkbox("I agree to participate in research", key="research_checkbox_unique_1") agree_to_pdpa = st.checkbox("I agree to the Personal Data Protection Policy (PDPA)", key="pdpa_checkbox_unique_1") show_pdpa_policy() if st.button("πŸš€ Create My Account", key="signup_button_unique_1"): if not agree_to_pdpa: st.error("You must agree to the PDPA to sign up.") elif fetch_student_data(new_username): # Check if username already exists st.error("Username already taken. Please choose another.") else: # Insert new student data insert_student_data( username=new_username, email=email, password=new_password, # In production, hash passwords before storing participate=participate ) student = fetch_student_data(new_username) if student: st.session_state.user_info = student st.success("Your account has been created! Redirecting...") navigate_to_mode() st.rerun() else: st.error("An error occurred during sign-up. Please try again later.") TASKS_TO_DATABASE = { "pre": [ "L1_T1_TAS_1", "TT_L1_T1_1", "TT_L1_T1_2", "TT_L1_T1_3", "L1_T2_TAS_1", "TT_L1_T2_1", "L1_T3_TAS_1", "TT_L1_T3_1", "L1_T4_TAS_1", "TT_L1_T4_1", "TT_L1_T4_2", "L1_T5_TAS_1", "TT_L1_T5_1", "TT_L1_T5_2", "L1_T6_TAS_1", "TT_L1_T6_1", "L1_T7_TAS_1", "TT_L1_T7_1", "L1_T8_TAS_1", "TT_L1_T8_1", "L1_T9_TAS_1", "L1_T10_TAS_1" ], "easy": [ "L1_T1_TAS_1", "TT_L1_T1_1", "TT_L1_T1_2", "TT_L1_T1_3", "L1_T2_TAS_1", "TT_L1_T2_1", "L1_T3_TAS_1", "TT_L1_T3_1", "L1_T4_TAS_1", "TT_L1_T4_1", "TT_L1_T4_2", "L1_T5_TAS_1", "TT_L1_T5_1", "TT_L1_T5_2", "L1_T6_TAS_1", "TT_L1_T6_1", "L1_T7_TAS_1", "TT_L1_T7_1", "L1_T8_TAS_1", "TT_L1_T8_1", "L1_T9_TAS_1", "L1_T10_TAS_1" ], "medium": [ "L2_T1_TAS_1", "TT_L2_T1_1", "TT_L2_T1_2", "TT_L2_T1_3", "L2_T2_TAS_1", "TT_L2_T2_1", "L2_T3_TAS_1", "TT_L2_T3_1", "TT_L2_T3_2", "L2_T4_TAS_1", "TT_L2_T4_1", "L2_T5_TAS_1", "TT_L2_T5_1", "L2_T6_TAS_1", "TT_L2_T6_1", "L2_T7_TAS_1", "TT_L2_T7_1", "L2_T8_TAS_1", "TT_L2_T8_1", "TT_L2_T8_2", "L2_T9_TAS_1", "L2_T10_TAS_1" ], "high": [ "L3_T1_TAS_1", "TT_L3_T1_1", "TT_L3_T1_2", "TT_L3_T1_3", "L3_T2_TAS_1", "TT_L3_T2_1", "L3_T3_TAS_1", "TT_L3_T3_1", "L3_T4_TAS_1", "TT_L3_T4_1", "TT_L3_T4_2", "L3_T5_TAS_1", "TT_L3_T5_1", "L3_T6_TAS_1", "TT_L3_T6_1", "L3_T7_TAS_1", "TT_L3_T7_1", "L3_T8_TAS_1", "TT_L3_T8_1", "L3_T9_TAS_1", "L3_T10_TAS_1" ], "post": [ "L3_T1_TAS_1", "TT_L3_T1_1", "TT_L3_T1_2", "TT_L3_T1_3", "L3_T2_TAS_1", "TT_L3_T2_1", "L3_T3_TAS_1", "TT_L3_T3_1", "L3_T4_TAS_1", "TT_L3_T4_1", "TT_L3_T4_2", "L3_T5_TAS_1", "TT_L3_T5_1", "L3_T6_TAS_1", "TT_L3_T6_1", "L3_T7_TAS_1", "TT_L3_T7_1", "L3_T8_TAS_1", "TT_L3_T8_1", "L3_T9_TAS_1", "L3_T10_TAS_1" ] } def get_next_undone_task(student_id): """ Fetch the next undone task by comparing 'tasks_to_do' with 'tasks_done', while treating all tasks in an optional group as completed if one is done. """ student = session.query(Student).filter_by(id=student_id).first() if not student: st.error("User not found in the database.") return None # Fetch tasks assigned to the student try: tasks_to_do = json.loads(student.tasks_to_do) if student.tasks_to_do else [] except json.JSONDecodeError: st.error("Error parsing tasks_to_do. Invalid JSON format.") return None # Fetch completed tasks completed_tasks = session.query(TasksDone.task_id).filter_by(student_id=student_id).all() completed_tasks = {task[0] for task in completed_tasks} # Convert to set for fast lookup # Define optional task groups OPTIONAL_TASK_GROUPS = { "L1_T2_TAS_1": ["L1_T2_TAS_1", "L1_T2_TAS_2"], "L1_T3_TAS_1": ["L1_T3_TAS_1", "L1_T3_TAS_2", "L1_T3_TAS_3"], "L1_T4_TAS_1": ["L1_T4_TAS_1", "L1_T4_TAS_2"], "L1_T5_TAS_1": ["L1_T5_TAS_1", "L1_T5_TAS_2", "L1_T5_TAS_3"], "L1_T6_TAS_1": ["L1_T6_TAS_1", "L1_T6_TAS_2", "L1_T6_TAS_3"], "L1_T7_TAS_1": ["L1_T7_TAS_1", "L1_T7_TAS_2"], "L1_T8_TAS_1": ["L1_T8_TAS_1", "L1_T8_TAS_2", "L1_T8_TAS_3"], "L1_T9_TAS_1": ["L1_T9_TAS_1", "L1_T9_TAS_2", "L1_T9_TAS_3"], "L2_T2_TAS_1": ["L2_T2_TAS_1", "L2_T2_TAS_2", "L2_T2_TAS_3"], "L2_T3_TAS_1": ["L2_T3_TAS_1", "L2_T3_TAS_2", "L2_T3_TAS_3"], "L2_T4_TAS_1": ["L2_T4_TAS_1", "L2_T4_TAS_2", "L2_T4_TAS_3"], "L2_T5_TAS_1": ["L2_T5_TAS_1", "L2_T5_TAS_2", "L2_T5_TAS_3"], "L2_T6_TAS_1": ["L2_T6_TAS_1", "L2_T6_TAS_2"], "L2_T7_TAS_1": ["L2_T7_TAS_1", "L2_T7_TAS_2", "L2_T7_TAS_3"], "L2_T8_TAS_1": ["L2_T8_TAS_1", "L2_T8_TAS_2", "L2_T8_TAS_3"], "L2_T9_TAS_1": ["L2_T9_TAS_1", "L2_T9_TAS_2"], "L3_T2_TAS_1": ["L3_T2_TAS_1", "L3_T2_TAS_2", "L3_T2_TAS_3"], "L3_T3_TAS_1": ["L3_T3_TAS_1", "L3_T3_TAS_2", "L3_T3_TAS_3"], "L3_T4_TAS_1": ["L3_T4_TAS_1", "L3_T4_TAS_2"], "L3_T5_TAS_1": ["L3_T5_TAS_1", "L3_T5_TAS_2", "L3_T5_TAS_3"], "L3_T6_TAS_1": ["L3_T6_TAS_1", "L3_T6_TAS_2", "L3_T6_TAS_3"], "L3_T7_TAS_1": ["L3_T7_TAS_1", "L3_T7_TAS_2", "L3_T7_TAS_3"], "L3_T8_TAS_1": ["L3_T8_TAS_1", "L3_T8_TAS_2"], "L3_T9_TAS_1": ["L3_T9_TAS_1", "L3_T9_TAS_2"], } # Mark optional groups as completed if one task in the group is done completed_task_groups = set() for key_task, task_group in OPTIONAL_TASK_GROUPS.items(): if any(task in completed_tasks for task in task_group): # If any task in the group is done completed_task_groups.update(task_group) # Mark all as completed # Find the first undone task for task in tasks_to_do: if isinstance(task, list): # Ignore task groups continue if task not in completed_tasks and task not in completed_task_groups: return task return None # No remaining tasks def start_next_activity(): """ Handles logic for "Start the activity". Redirects to the next undone task or tutorial page. """ user_info = st.session_state.get('user_info') if not user_info or not hasattr(user_info, 'id'): st.error("No logged-in user found. Please log in first.") return next_task = get_next_undone_task(user_info.id) if next_task: if next_task in pages: # βœ… If it's a tutorial page, go directly to it st.session_state.page = next_task else: # βœ… If it's a normal task, go to `task_1` and store the task st.session_state.task_selection = next_task st.session_state.page = "task_1" st.rerun() else: st.success("πŸŽ‰ All assigned tasks have been completed!") def user_dashboard_page(): st.title("User Dashboard") # Custom CSS Styles styles = """ """ st.markdown(styles, unsafe_allow_html=True) # Retrieve logged-in student data student = st.session_state.get('user_info') # Ensure 'user_info' contains the student object if not student: st.error("No user is currently logged in.") return st.markdown(f"

Welcome, {student.username}!

", unsafe_allow_html=True) # Progress Section st.markdown('

My Progress

', unsafe_allow_html=True) activities_completed = st.session_state.get('activities_completed', 0) progress_percentage = min(activities_completed / 10, 1.0) # Ensure progress doesn't exceed 100% st.progress(progress_percentage) if st.button("Start the activity"): start_next_activity() st.rerun() # Create a layout with two columns: Left for Metrics & Errors, Right for Buttons col1, col2 = st.columns([3, 1]) with col1: # Display Metrics words_count = st.session_state.get('words_count', 0) sentences_count = st.session_state.get('sentences_count', 0) learning_time = st.session_state.get('learning_time', 0) metrics_html = f"""

My Metrics

Words

{words_count}

Sentences

{sentences_count}

Learning Time

{learning_time} minutes

""" st.markdown(metrics_html, unsafe_allow_html=True) # Fetch Errors for the Logged-in Student try: errors_obj = session.query(Errors).filter_by(student_id=student.id).one_or_none() except SQLAlchemyError as e: st.error(f"Error fetching errors: {e}") errors_obj = None # grammar errors and spelling errors if errors_obj: gram_err = errors_obj.gram_err or {} spell_err = errors_obj.spell_err or [] else: gram_err = {} spell_err = [] with col2: # Right-side buttons st.markdown('
', unsafe_allow_html=True) if st.button("Grammar Practice"): st.info("Navigating to Grammar Practice... (placeholder)") if st.button("Spelling Practice"): navigate_to_spelling() st.rerun() st.markdown('
', unsafe_allow_html=True) def navigate_to_spelling(): st.session_state.page = 'spelling_practice' def show_pdpa_policy(): st.markdown("""
View Personal Data Protection Policy

Personal Data Protection Policy

Introduction

At GrammarTool, we are committed to protecting the privacy and personal data of our users...

What Personal Data We Collect

How We Use Your Personal Data

The personal data we collect will be used for the following purposes:

Data Security

We implement appropriate technical and organizational measures to protect your personal data from unauthorized access...

Your Rights

You have the right to request access to your personal data, corrections, and deletion where applicable.

Contact us at [Contact Email] for any PDPA-related inquiries.

""", unsafe_allow_html=True) def render_progress_bar(): steps = ["login", "personal_info", "proficiency", "self_assessment", "user_dashboard"] step_labels = ["Login/Registration", "Personal Information", "Proficiency Test", "Self-Assessment", "User Dashboard"] current_step = steps.index(st.session_state.page) if st.session_state.page in steps else 0 if st.session_state.get('render_progress_bar', False) and st.session_state.page in steps: progress_percentage = (current_step + 1) / len(steps) st.progress(progress_percentage) st.write(f"Step {current_step + 1} of {len(steps)}: {step_labels[current_step]}") non_count_nouns_list = [ "advice", "anger", "beauty", "bravery", "calm", "care", "chaos", "comfort", "courage", "curiosity", "darkness", "dignity", "energy", "enthusiasm", "evil", "faith", "fame", "freedom", "friendship", "fun", "generosity", "goodness", "happiness", "health", "honesty", "honor", "hope", "hospitality", "humor", "importance", "independence", "intelligence", "joy", "justice", "kindness", "knowledge", "laughter", "laziness", "liberty", "logic", "love", "luck", "maturity", "mercy", "modesty", "motivation", "mystery", "obedience", "patience", "peace", "perfection", "pride", "progress", "relief", "respect", "satisfaction", "shame", "simplicity", "sincerity", "skill", "success", "support", "talent", "trust", "truth", "understanding", "violence", "wealth", "wisdom", "youth","noney","time", "air", "aluminum", "asphalt", "brass", "bread", "brick", "bronze", "butter", "cardboard", "cement", "cheese", "chocolate", "clay", "cloth", "concrete", "copper", "cotton", "cream", "dirt", "dust", "fabric", "flour", "foam", "fuel", "gasoline", "glass", "gold", "gravel", "grease", "honey", "hydrogen", "ice", "iron", "jelly", "leather", "linen", "lumber", "margarine", "mayonnaise", "meat", "milk", "nylon", "oil", "oxygen", "paper", "plastic", "platinum", "pollen", "rubber", "salt", "sand", "silk", "silver", "soap", "soil", "spice", "steel", "stone", "sugar", "syrup", "tea", "tofu", "velvet", "water", "wax", "wool", "applause", "clothing", "equipment", "feedback", "footwear", "furniture", "garbage", "gear", "hardware", "jewelry", "luggage", "machinery", "mail", "makeup", "merchandise", "money", "news", "pollution", "software", "stationery", "traffic", "trash", "transportation", "waste", "work","advice" "accounting", "acting", "advertising", "baking", "boating", "camping", "catering", "chess", "dancing", "diving", "driving", "editing", "engineering", "farming", "fencing", "fishing", "gardening", "golf", "hiking", "homework", "hunting", "jogging", "journalism", "knitting", "law", "marketing", "medicine", "mining", "nursing", "painting", "photography", "reading", "sailing", "sculpting", "shopping", "singing", "skiing", "sleeping", "snorkeling", "surfing", "swimming", "teaching", "tourism", "training", "typing", "walking", "writing", "yoga", "anatomy", "architecture", "astronomy", "biology", "botany", "chemistry", "economics", "engineering", "environmentalism", "genetics", "geography", "geology", "history", "linguistics", "mathematics", "medicine", "microbiology", "music", "nutrition", "physics", "physiology", "psychology", "sociology", "statistics", "zoology", "autumn", "climate", "cold", "dew", "drizzle", "fog", "frost", "hail", "heat", "humidity", "lightning", "mist", "moisture", "rain", "sleet", "smog", "snow", "spring", "sunshine", "thunder", "weather", "winter", "aspirin", "blood", "chalk", "cream", "deodorant", "insulin", "medicine", "lotion", "mucus", "oxygen", "shampoo", "soap", "toothpaste", "wax", "bacon", "butter", "caviar", "cereal", "cheese", "coffee", "cottage cheese", "cream", "flour", "fruit", "garlic", "ginger", "honey", "ketchup", "lamb", "lettuce", "margarine", "mayonnaise", "meat", "milk", "mustard", "oil", "pasta", "pepper", "pork", "poultry", "rice", "salt", "sauce", "seafood", "spinach", "sugar", "tea", "tofu", "turkey", "vinegar", "water", "whiskey", "wine", "yogurt", "antibiotics", "bandage", "chemotherapy", "cotton", "cure", "diagnosis", "insulin", "ointment", "penicillin", "physiotherapy", "plasma", "radiation", "serum", "stamina", "surgery", "treatment", "vaccination", "vaccine", "biodiversity", "conservation", "deforestation", "erosion", "habitat", "land", "landscape", "nature", "pollution", "rainfall", "recycling", "silt", "soil", "sunlight", "temperature", "vegetation", "wildlife", "wind" ] # Define lists for common time and location markers time_markers = [ "morning", "the afternoon", "evening", "night", "midnight", "noon", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "weekend", "holiday", "Christmas", "New Year's Day", "summer", "winter", "spring", "fall", "today", "tomorrow", "yesterday" ] location_markers = [ "school", "office", "home", "hospital", "airport", "station", " the park", "beach", "restaurant", "cafe", "museum", "city", "village", "country", "town", "building", "mall", "theater", "library", "shop", "market", "street", "road", "highway", "bridge", "intersection", "plaza" ] modal_verbs_no_to = ['can', 'could', 'may', 'might', 'shall', 'should', 'will', 'would', 'must'] modal_verbs_with_to = ['have to', 'need to', 'ought to', 'be able to', 'used to'] relative_pronouns = ['who', 'whom', 'whose', 'which', 'that', 'where', 'when'] countable_singular_plural_pairs = { "person": "people", "man": "men", "woman": "women", "child": "children", "mouse": "mice", "foot": "feet", "tooth": "teeth", "goose": "geese" } # Verbs followed by Gerunds (Verb + -ing) verbs_followed_by_gerunds = [ "admit", "admits", "admitted", "anticipate", "anticipates", "anticipated", "appreciate", "appreciates", "appreciated", "avoid", "avoids", "avoided", "begin", "begins", "began", "consider", "considers", "considered", "continue", "continues", "continued", "delay", "delays", "delayed", "deny", "denies", "denied", "discuss", "discusses", "discussed", "dislike", "dislikes", "disliked", "enjoy", "enjoys", "enjoyed", "escape", "escapes", "escaped", "finish", "finishes", "finished", "imagine", "imagines", "imagined", "involve", "involves", "involved", "keep", "keeps", "kept", "mention", "mentions", "mentioned", "mind", "minds", "minded", "miss", "misses", "missed", "postpone", "postpones", "postponed", "practice", "practices", "practiced", "quit", "quits", "quit", "recall", "recalls", "recalled", "recommend", "recommends", "recommended", "regret", "regrets", "regretted", "resist", "resists", "resisted", "risk", "risks", "risked", "suggest", "suggests", "suggested", "understand", "understands", "understood","begin", "begins", "began", "start", "starts", "started", "continue", "continues", "continued", "hate", "hates", "hated", "like", "likes", "liked", "love", "loves", "loved", "prefer", "prefers", "preferred", "attempt", "attempts", "attempted","forget", "forgets", "forgot", "remember", "remembers", "remembered", "stop", "stops", "stopped", "try", "tries", "tried", "regret", "regrets", "regretted", "mean", "means", "meant", "go", "goes", "went" ] possessive_pronouns = {"my", "mine", "your", "yours", "his", "her", "hers", "its", "our", "ours", "their", "theirs"} # Verbs followed by Infinitives (Verb + to + infinitive) verbs_followed_by_infinitives = [ "agree", "agrees", "agreed", "afford", "affords", "afforded", "appear", "appears", "appeared", "arrange", "arranges", "arranged", "ask", "asks", "asked", "attempt", "attempts", "attempted", "care", "cares", "cared", "choose", "chooses", "chose", "claim", "claims", "claimed", "decide", "decides", "decided", "demand", "demands", "demanded", "deserve", "deserves", "deserved", "expect", "expects", "expected", "fail", "fails", "failed", "forget", "forgets", "forgot", "happen", "happens", "happened", "hesitate", "hesitates", "hesitated", "hope", "hopes", "hoped", "intend", "intends", "intended", "learn", "learns", "learned", "manage", "manages", "managed", "mean", "means", "meant", "offer", "offers", "offered", "plan", "plans", "planned", "prepare", "prepares", "prepared", "pretend", "pretends", "pretended", "promise", "promises", "promised", "refuse", "refuses", "refused", "seem", "seems", "seemed", "struggle", "struggles", "struggled", "tend", "tends", "tended", "threaten", "threatens", "threatened", "want", "wants", "wanted", "wish", "wishes", "wished","begin", "begins", "began", "start", "starts", "started", "continue", "continues", "continued", "hate", "hates", "hated", "like", "likes", "liked", "love", "loves", "loved", "prefer", "prefers", "preferred", "attempt", "attempts", "attempte","forget", "forgets", "forgot", "remember", "remembers", "remembered", "stop", "stops", "stopped", "try", "tries", "tried", "regret", "regrets", "regretted", "mean", "means", "meant", "go", "goes", "went" ] extra_determiners = [ "the", "a", "an", "this", "that", "these", "those", "my", "your", "his", "her", "its", "our", "their", "some", "any", "each", "every", "either", "neither", "few", "many", "much", "several", "all", "both", "half", "no", "one", "two", "three", # Add more numbers if needed "such", "what", "which" ] coordinating_conjunctions = {"and", "or", "but", "for", "nor", "yet", "so"} subordinating_conjunctions = { "because", "although", "since", "if", "while", "before", "after", "when", "as", "unless", "until", "whereas", "though", "even though", "as long as", "as soon as", "so that", "in order that" } ERROR_CODES_WITH_DISTRACTIONS = { "VERB:INFL", "NOUN:INFL", "PRON:CASE_SUB", "PRON:REL_WHICH", "PRON:REL_WHO", "PRON:REL_WHOM", "PRON:REL_WHOSE", "PRON:REL_THAT", "PRON:REL_WHERE", "PRON:REL_WHEN", "PRON:GEN", "DET:CON_A", "DET:CON_AN", "DET:POSSESSIVE_ERROR", "ADJ:FORM", "ADV:COMP", "ADV:SUP", "ADV:FORM", "ADV:FORM_ERR", "PREP:IN", "ORTH:CAP", "MORPH:ADJ_TO_ADV", "MORPH:ADV_TO_ADJ", "MORPH:COMP_SUPER", "MORPH:SPELL_NOUN", "MORPH:ADJ_ADV_FORM", "CONJ:COORD", "CONJ:SUBORD", "NOUN:POS_MISS", "NOUN:POS_MISS/IN", "DET:IT_ITS", "DET:ITS_IT", "ORTH:HYP", "NOUN:POSS" } ERROR_CODES_SCRAMBLE_POS = { "WO", "PREP:MISS", "DET:MISS", "VERB:MISS", "VERB:FORM_OTHER", "OTHER", "NOUN", "EXTRA:TO", "DET:EXTRA", "PREP:EXTRA", "MORPH:GEN", "CONJ:GEN", "CONJ:MISS_COMPOUND", "CONJ:MISS_COMPLEX", "CONJ:MISS_COORD", "CONJ:MISS_SUBORD" } punctuation_errors = { "PUNCT:EXTRA_COMMA", "PUNCT:MISSING_COMMA", "PUNCT:EXTRA_PERIOD", "PUNCT:MISSING_PERIOD", "PUNCT:COMMA_SPLICE", "PUNCT:SEMICOLON", "PUNCT:EXTRA_COLON", "PUNCT:MISSING_COLON", "PUNCT:EXTRA_SEMICOLON", "PUNCT:MISSING_SEMICOLON", "PUNCT:GEN", "MISSING:COMMA_SEQ", "MISSING:COMMA_COMPOUND", "MISSING:COMMA_COMPLEX", "MISSING:COMMA" } # Define error codes related to verb form, tense, and SVA VERB_ERROR_CODES = { "VERB:TENSE_PRSIM", "VERB:TENSE_PRCON", "VERB:TENSE_PRPER", "VERB:TENSE_PRPERCON", "VERB:TENSE_PASIM", "VERB:TENSE_PACON", "VERB:TENSE_PAPER", "VERB:TENSE_PAPERCON", "VERB:TENSE_FUSIM", "VERB:TENSE_FUCON", "VERB:TENSE_FUPER", "VERB:TENSE_FUPERCON", "VERB:TENSE_PRSIMPAS", "VERB:TENSE_PRCONPAS", "VERB:TENSE_PRPERPAS", "VERB:TENSE_PASIMPAS", "VERB:TENSE_PACONPAS", "VERB:TENSE_PAPERPAS", "VERB:TENSE_FUSIMPAS", "VERB:TENSE_FUCONPAS", "VERB:TENSE_FUPERPAS", "VERB:TENSE_UNPASS", "VERB:TENSE_UNACT", "VERB:FORM_VERB:FORM_MODDEDUC", "VERB:SVA_PRSIM", "VERB:SVA_PRCON", "VERB:SVA_PRPER", "VERB:SVA_PRPERCON", "VERB:SVA_PASIM", "VERB:SVA_PACON", "VERB:SVA_PAPER", "VERB:SVA_PAPERCON", "VERB:SVA_FUSIM", "VERB:SVA_FUCON", "VERB:SVA_FUPER", "VERB:SVA_FUPERCON", "VERB:SVA_PRSIMPAS", "VERB:SVA_PRCONPAS", "VERB:SVA_PRPERPAS", "VERB:SVA_PASIMPAS", "VERB:SVA_PACONPAS", "VERB:SVA_PAPERPAS", "VERB:SVA_FUSIMPAS", "VERB:SVA_FUCONPAS", "VERB:SVA_FUPERPAS", "VERB:FORM_PRSIM", "VERB:FORM_PRCON", "VERB:FORM_PRPER", "VERB:FORM_PRPERCON", "VERB:FORM_PASIM", "VERB:FORM_PACON", "VERB:FORM_PAPER", "VERB:FORM_PAPERCON", "VERB:FORM_FUSIM", "VERB:FORM_FUCON", "VERB:FORM_FUPER", "VERB:FORM_FUPERCON", "VERB:FORM_PRSIMPAS", "VERB:FORM_PRCONPAS", "VERB:FORM_PRPERPAS", "VERB:FORM_PASIMPAS", "VERB:FORM_PACONPAS", "VERB:FORM_PAPERPAS", "VERB:FORM_FUSIMPAS", "VERB:FORM_FUCONPAS", "VERB:FORM_FUPERPAS", "VERB:FORM_UNPASS", "VERB:FORM_UNACT" } NOUN_ERRORS = { "NOUN:SG", "NOUN:PL", "NOUN:COUNT", "NOUN:NUM_UNCOUNT","NOUN:POSS"} def main(): # Initialize session state variables if 'page' not in st.session_state: st.session_state.page = 'task_1' if 'show_learn_more' not in st.session_state: st.session_state.show_learn_more = False if 'analysis_done' not in st.session_state: st.session_state.analysis_done = False if 'user_input_task1' not in st.session_state: st.session_state.user_input_task1 = '' if 'accuracy_score' not in st.session_state: st.session_state['accuracy_score'] = 0 if st.session_state.page == 'task_1': task_1_page() # Additional pages can be added as needed # Define mapping function for tenses def map_verb_error_label(label): mapping = { 'Present Simple Active': 'PRSIM', 'Present Continuous Active': 'PRCON', 'Present Perfect Active': 'PRPER', 'Present Perfect Continuous Active': 'PRPERCON', 'Past Simple Active': 'PASIM', 'Past Continuous Active': 'PACON', 'Past Perfect Active': 'PAPER', 'Past Perfect Continuous Active': 'PAPERCON', 'Future Simple Active': 'FUSIM', 'Future Continuous Active': 'FUCON', 'Future Perfect Active': 'FUPER', 'Future Perfect Continuous Active': 'FUPERCON', 'Present Simple Passive': 'PRSIMPAS', 'Present Continuous Passive': 'PRCONPAS', 'Present Perfect Passive': 'PRPERPAS', 'Past Simple Passive': 'PASIMPAS', 'Past Continuous Passive': 'PACONPAS', 'Past Perfect Passive': 'PAPERPAS', 'Future Simple Passive': 'FUSIMPAS', 'Future Continuous Passive': 'FUCONPAS', 'Future Perfect Passive': 'FUPERPAS', 'Modals of deduction': 'MODDEDUC', 'Unknown Passive': 'UNPASS', 'Unknown Active': 'UNACT' } return mapping.get(label, 'SIM') # Expand contractions def expand_contractions(sentence): contractions = { "isn't": "is not", "aren't": "are not", "wasn't": "was not", "weren't": "were not", "hasn't": "has not", "haven't": "have not", "hadn't": "had not", "won't": "will not", "wouldn't": "would not", "can't": "cannot", "couldn't": "could not", "shouldn't": "should not", "doesn't": "does not", "don't": "do not", "didn't": "did not" } for contraction, expanded in contractions.items(): sentence = sentence.replace(contraction, expanded) return sentence # Detect passive voice based on "to be" forms + past participle (VBN) def is_passive_voice(tokens): be_forms = {"is", "are", "was", "were", "be", "being", "been", "is not", "are not", "was not", "were not", "will be", "will not be"} # Check for a "to be" form followed by a VBN (past participle) for i, token in enumerate(tokens[:-1]): if token.text.lower() in be_forms and tokens[i + 1].tag_ == 'VBN': return True return False def determine_tense(sentence): expanded_sentence = expand_contractions(sentence) # Expand contractions doc = nlp(expanded_sentence) tokens = [token for token in doc] auxiliaries = [token for token in tokens if token.dep_ in {'aux', 'auxpass'}] verbs = [token for token in tokens if token.pos_ == 'VERB'] # Check if the sentence is passive if is_passive_voice(tokens): voice = "Passive" # Check for passive tenses if any(aux.text.lower() in {"have", "has"} for aux in auxiliaries) and any(verb.tag_ == 'VBN' for verb in verbs): tense = "Present Perfect Passive" elif any(aux.text.lower() == "will" for aux in auxiliaries) and any(verb.tag_ == 'VBN' for verb in verbs): tense = "Future Simple Passive" elif any(aux.text.lower() == "had" for aux in auxiliaries): tense = "Past Perfect Passive" elif any(aux.text.lower() == "will have been" for aux in auxiliaries): tense = "Future Perfect Passive" elif any(aux.text.lower() in {"is", "are", "am"} for aux in auxiliaries) and any(verb.tag_ == 'VBG' for verb in verbs): tense = "Present Continuous Passive" elif any(aux.text.lower() in {"was", "were"} for aux in auxiliaries) and any(verb.tag_ == 'VBG' for verb in verbs): tense = "Past Continuous Passive" elif any(aux.text.lower() in {"is", "are", "am"} for aux in auxiliaries): tense = "Present Simple Passive" elif any(aux.text.lower() in {"was", "were"} for aux in auxiliaries): tense = "Past Simple Passive" else: tense = "Unknown Passive" else: # If not passive, it’s active voice = "Active" # Check for active tenses if any(aux.text.lower() in {"have", "has"} for aux in auxiliaries) and any(verb.tag_ == 'VBG' for verb in verbs): tense = "Present Perfect Continuous Active" elif any(aux.text.lower() == "had" for aux in auxiliaries) and any(verb.tag_ == 'VBG' for verb in verbs): tense = "Past Perfect Continuous Active" elif any(aux.text.lower() in {"could", "must", "may", "might", "can't"} for aux in auxiliaries) and \ any(aux.text.lower() == "have" for aux in auxiliaries) and \ any(verb.tag_ == 'VBN' for verb in verbs): tense = "Modals of deduction" elif any(aux.text.lower() in {"am", "is", "are"} for aux in auxiliaries) and any(verb.tag_ == 'VBG' for verb in verbs): tense = "Present Continuous Active" elif any(aux.text.lower() == "will" for aux in auxiliaries) and any(aux.text.lower() == "have" for aux in auxiliaries) and any(verb.tag_ == 'VBN' for verb in verbs): tense = "Future Perfect Active" elif any(aux.text.lower() in {"have", "has"} for aux in auxiliaries) and any(verb.tag_ == 'VBN' for verb in verbs): tense = "Present Perfect Active" elif any(aux.text.lower() in {"was", "were"} for aux in auxiliaries) and any(verb.tag_ == 'VBG' for verb in verbs): tense = "Past Continuous Active" elif any(aux.text.lower() == "had" for aux in auxiliaries) and any(verb.tag_ == 'VBN' for verb in verbs): tense = "Past Perfect Active" elif any(aux.text.lower() == "will" for aux in auxiliaries) and all(verb.tag_ == 'VB' for verb in verbs): tense = "Future Simple Active" elif any(aux.text.lower() == "will" for aux in auxiliaries) and any(verb.tag_ == 'VBG' for verb in verbs): tense = "Future Continuous Active" elif all(verb.tag_ == 'VBD' for verb in verbs) and not auxiliaries: tense = "Past Simple Active" elif all(verb.tag_ in {'VB', 'VBP', 'VBZ'} for verb in verbs) and not auxiliaries: tense = "Present Simple Active" else: tense = "Unknown Active" # Map tense to predefined codes mapped_tense = map_verb_error_label(f"{tense}") return mapped_tense # Helper function for separating punctuation def separate_punctuation(text): # Separate punctuation from words both before and after using regex text = re.sub(r'(\w)([.,:;!?])', r'\1 \2', text) # Separate punctuation after words text = re.sub(r'([.,:;!?])(\w)', r'\1 \2', text) # Separate punctuation before words return text.strip() # Helper function for separating punctuation def separate_punctuation(text): # Separate punctuation from words both before and after using regex text = re.sub(r'(\w)([.,:;!?])', r'\1 \2', text) # Separate punctuation after words text = re.sub(r'([.,:;!?])(\w)', r'\1 \2', text) # Separate punctuation before words return text.strip() def map_error_code(error_type, sentence, original_tokens, corrected_tokens): """ Maps error codes for punctuation and other errors, calling classify_comma_error for missing commas. """ # Separate punctuation from text in original and corrected tokens original_text = separate_punctuation(" ".join([tok.text if hasattr(tok, 'text') else str(tok) for tok in original_tokens if isinstance(tok, (str, spacy.tokens.Token))])) corrected_text = separate_punctuation(" ".join([tok.text if hasattr(tok, 'text') else str(tok) for tok in corrected_tokens if isinstance(tok, (str, spacy.tokens.Token))])) # Ensure that original_tokens and corrected_tokens are always converted to lowercase original_tokens_lower = [str(token).lower() for token in original_tokens if isinstance(token, (str, spacy.tokens.Token))] corrected_tokens_lower = [str(token).lower() for token in corrected_tokens if isinstance(token, (str, spacy.tokens.Token))] # Syntax errors if error_type == "WO": if len(original_tokens) > 1 and len(corrected_tokens) > 1: # Ensure that we only access `dep_` if the token has it if any(hasattr(tok, 'dep_') and tok.dep_ == "advmod" for tok in original_tokens) or \ any(hasattr(tok, 'dep_') and tok.dep_ == "advmod" for tok in corrected_tokens): return "SYNTAX:WORD_ORDER" if any(hasattr(tok, 'dep_') and tok.dep_ == "ccomp" for tok in original_tokens) or \ any(hasattr(tok, 'dep_') and tok.dep_ == "xcomp" for tok in corrected_tokens): return "SYNTAX:CLAUSE_POS" else: return "WO:GEN" # General syntax error # Punctuation-specific handling if ',' in original_text and ',' not in corrected_text: return "PUNCT:EXTRA_COMMA" elif ',' in corrected_text and ',' not in original_text: # Call classify_comma_error for detailed comma classification return "PUNCT:MISSING_COMMA" elif ':' in original_text and ':' not in corrected_text: return "PUNCT:EXTRA_COLON" elif ':' in corrected_text and ':' not in original_text: return "PUNCT:MISSING_COLON" elif ':' in original_text and ';' not in corrected_text: return "PUNCT:EXTRA_SEMICOLON" elif ':' in corrected_text and ';' not in original_text: return "PUNCT:MISSING_SEMICOLON" elif original_text.endswith('.') and not corrected_text.endswith('.'): return "PUNCT:EXTRA_PERIOD" elif corrected_text.endswith('.') and not original_text.endswith('.'): return "PUNCT:MISSING_PERIOD" # Orthography-specific handling if error_type == "ORTH": # Detect missing space or double space issues if " " in original_text or " " in corrected_text or re.search(r"[a-z][A-Z]", original_text): return "ORTH:SPACE" # Missing space or double space error # Detect incorrect capitalization elif original_text.lower() == corrected_text.lower() and original_text != corrected_text: return "ORTH:CAP" # General capitalization error # Detect incorrect capitalization in proper nouns (e.g., "new york" -> "New York") elif original_text.istitle() == False and corrected_text.istitle() and re.search(r'\b[A-Z][a-z]+\b', corrected_text): return "ORTH:CAP_PROP" # Proper noun capitalization error else: return "ORTH:GEN" # Handling determiner and noun errors with improved logic for counting if error_type == "DET" and not original_tokens: return "DET:MISS" # Missing determiner if error_type == "VERB" and "to" in corrected_text and original_text.endswith("ing"): return "VERB:INFIN" # Infinitive required # Other error type classifications if error_type == "NOUN": original_word = original_text.strip(string.punctuation) corrected_word = corrected_text.strip(string.punctuation) if corrected_word.endswith("'s") and not original_word.endswith("'s"): return "NOUN:POS_MISS/IN" elif original_word.endswith("'s") and not corrected_word.endswith("'s"): return "NOUN:POS_EXTRA" elif original_word.endswith("s") and not corrected_word.endswith("s"): return "NOUN:SG" # Should be singular elif not original_word.endswith("s") and corrected_word.endswith("s"): return "NOUN:PL" # Should be plural elif original_word.endswith("s") and corrected_word in non_count_nouns_list: return "NOUN:NUM_UNCOUNT" # Plural form of an uncountable noun else: return "NOUN" if error_type == "NOUN:NUM": original_word = original_text.strip(string.punctuation) corrected_word = corrected_text.strip(string.punctuation) if original_word.endswith("s") and corrected_word in non_count_nouns_list: return "NOUN:NUM_UNCOUNT" # Plural form of an uncountable noun # Check for possessive noun errors (NOUN:POS) elif corrected_word.endswith("'s") and not original_word.endswith("'s"): return "NOUN:POS_MISS" # Missing possessive elif original_word.endswith("'s") and not corrected_word.endswith("'s"): return "NOUN:POS_EXTRA" # Extra possessive elif original_word.endswith("s") and not corrected_word.endswith("s"): return "NOUN:SG" # Should be singular elif not original_word.endswith("s") and corrected_word.endswith("s"): return "NOUN:PL" # Should be plural elif original_word in non_count_nouns_list and not corrected_word in non_count_nouns_list: return "NOUN:COUNT" # Incorrect use of countable noun else: return "NOUN:NUM" if error_type == "NOUN:INFL": return "NOUN:INFL" if error_type == "VERB:INFL": return "VERB:INFL" if error_type == "WO": return "WO" if error_type == "NOUN:POSS": return "NOUN:POSS" elif error_type == "DET": original_tokens_lower = [str(token).lower() for token in original_tokens] corrected_tokens_lower = [str(token).lower() for token in corrected_tokens] # Check if original_text is empty or only contains whitespace (indicating a missing determiner) if original_text.strip() == "": return "DET:MISS" # Missing determiner elif corrected_text.lower() == "an" and original_text.lower() == "a": return "DET:CON_AN" # Incorrect "a" instead of "an" elif corrected_text.lower() == "a" and original_text.lower() == "an": return "DET:CON_A" # Incorrect "an" instead of "a" if any(tok.lower() in extra_determiners for tok in original_tokens if isinstance(tok, str)) and not any(tok.lower() in extra_determiners for tok in corrected_tokens if isinstance(tok, str)): return "DET:EXTRA" # Check if "Its's" exists in the original and "Its" exists in the corrected text elif "its's" in original_tokens_lower and "its" in corrected_tokens_lower: return "DET:POSSESSIVE_ERROR" # Incorrect possessive determiner "Its's" if ("it" in original_tokens_lower or "it's" in original_tokens_lower or "its'" in original_tokens_lower) and "its" in corrected_tokens_lower: return "DET:IT_ITS" # Incorrect determiner "it" instead of "its" if "its" in original_tokens_lower and ("it" in corrected_tokens_lower or "it's" in corrected_tokens_lower or "its'" in corrected_tokens_lower): return "DET:ITS_IT" # Incorrect determiner "its" instead of "it" # Modal pronoun substitution elif any(token in possessive_pronouns for token in original_tokens) and any(token in possessive_pronouns for token in corrected_tokens): # Ensure the original and corrected tokens are not the same if set(original_tokens).intersection(possessive_pronouns) != set(corrected_tokens).intersection(possessive_pronouns): return "DET:POSSESSIVE_PRONOUN_SUBSTITUTION" elif original_text.endswith("'s") and not corrected_text.endswith("'s"): return "PRON:POSS_EXTRA" # Extra possessive elif not original_text.endswith("'s") and corrected_text.endswith("'s"): return "PRON:POSS_MISS" # Missing possessive elif original_text in relative_pronouns and corrected_text == "which": return "PRON:REL_WHICH" # Incorrect relative pronoun corrected to "which" elif original_text in relative_pronouns and corrected_text == "who": return "PRON:REL_WHO" # Incorrect relative pronoun corrected to "who" elif original_text in relative_pronouns and corrected_text == "whom": return "PRON:REL_WHOM" # Incorrect relative pronoun corrected to "whom" elif original_text in relative_pronouns and corrected_text == "whose": return "PRON:REL_WHOSE" # Incorrect relative pronoun corrected to "whose" elif original_text in relative_pronouns and corrected_text == "that": return "PRON:REL_THAT" # Incorrect relative pronoun corrected to "that" elif original_text in relative_pronouns and corrected_text == "where": return "PRON:REL_WHERE" # Incorrect relative pronoun corrected to "where" elif original_text in relative_pronouns and corrected_text == "when": return "PRON:REL_WHEN" # Incorrect relative pronoun corrected to "when" else: return "DET:IN" # General determiner error # Modal verb errors if error_type == "VERB": if "can" in original_text and "could" in corrected_text: return "VERB:MOD_CAN" elif "must" in original_text and "should" in corrected_text: return "VERB:MOD_MUST" elif "should" in original_text and any(word in corrected_text for word in ["ought to", "had better"]): return "VERB:MOD_ADVICE" elif any(word in original_text for word in ["must have", "might have", "could have"]): return "VERB:MOD_OF_DEDUCTION" elif error_type == "VERB:INFL": return "VERB:INFL" elif original_text.strip() == "": return "VERB:MISS" # Missing main verb error if error_type == "VERB:FORM": original_word = original_text.strip(string.punctuation) corrected_word = corrected_text.strip(string.punctuation) # Check for verb gerund error (verb should change to gerund form) if any(word in corrected_word for word in verbs_followed_by_gerunds) and not original_text.endswith('ing') and corrected_text.endswith('ing'): return "VERB:GERUND_ERROR" # Gerund form required (e.g., "I enjoy walking") # Check for verb infinitive error (verb should change to infinitive form) if any(word in original_word for word in verbs_followed_by_infinitives) and "to" not in original_word and "to" in corrected_word: return "VERB:INFINITIVE_ERROR" # Infinitive form required (e.g., "I want to go home") # Check for modal verb error (no "to" after modal verbs like "should", "must", etc.) if any(modal in original_word for modal in modal_verbs_no_to) and "to" in original_word: return "VERB:MODAL_NO_TO_ERROR" # Incorrect use of "to" after modal verbs like "should" # Check for modal verb error (missing "to" for verbs that require it, like "have to", "ought to") if any(modal_with_to.split()[0] in original_word for modal_with_to in modal_verbs_with_to) and "to" not in original_word and "to" in corrected_word: return "VERB:MODAL_MISSING_TO_ERROR" # Missing "to" for modal verbs that require it (e.g., "ought to") # If none of the specific rules match, return a general verb form error return "VERB:FORM_OTHER" elif error_type == "PRON": if original_text in {"me", "him", "her"}: return "PRON:CASE_OBJ" # Objective case error elif original_text in {"I", "he", "she"}: return "PRON:CASE_SUB" # Subjective case error elif original_text.endswith("'s") and not corrected_text.endswith("'s"): return "PRON:POSS_EXTRA" # Extra possessive elif not original_text.endswith("'s") and corrected_text.endswith("'s"): return "PRON:POSS_MISS" # Missing possessive elif original_text in relative_pronouns and corrected_text == "which": return "PRON:REL_WHICH" # Incorrect relative pronoun corrected to "which" elif original_text in relative_pronouns and corrected_text == "who": return "PRON:REL_WHO" # Incorrect relative pronoun corrected to "who" elif original_text in relative_pronouns and corrected_text == "whom": return "PRON:REL_WHOM" # Incorrect relative pronoun corrected to "whom" elif original_text in relative_pronouns and corrected_text == "whose": return "PRON:REL_WHOSE" # Incorrect relative pronoun corrected to "whose" elif original_text in relative_pronouns and corrected_text == "that": return "PRON:REL_THAT" # Incorrect relative pronoun corrected to "that" elif original_text in relative_pronouns and corrected_text == "where": return "PRON:REL_WHERE" # Incorrect relative pronoun corrected to "where" elif original_text in relative_pronouns and corrected_text == "when": return "PRON:REL_WHEN" # Incorrect relative pronoun corrected to "when" # Check if a possessive pronoun in the original is replaced with another in the corrected text if any(token in possessive_pronouns for token in original_tokens) and any(token in possessive_pronouns for token in corrected_tokens): # Ensure the original and corrected tokens are not the same if set(original_tokens).intersection(possessive_pronouns) != set(corrected_tokens).intersection(possessive_pronouns): return "DET:POSSESSIVE_PRONOUN_SUBSTITUTION" else: return "PRON:GEN" # General pronoun error # Preposition errors if error_type == "PREP": original_tokens_lower = [str(token).lower() for token in original_tokens if isinstance(token, str)] corrected_tokens_lower = [str(token).lower() for token in corrected_tokens if isinstance(token, str)] original_word = original_text.strip(string.punctuation) corrected_word = corrected_text.strip(string.punctuation) doc_original = nlp(original_text) doc_corrected = nlp(corrected_text) # Check for coordinating conjunction errors if any(word in coordinating_conjunctions for word in original_tokens_lower): return "CONJ:COORD" # Coordinating conjunction error # Check for subordinating conjunction errors elif any(word in subordinating_conjunctions for word in original_tokens_lower): return "CONJ:SUBORD" # Subordinating conjunction error # Check for missing conjunctions in compound sentences elif "compound" in original_text.lower(): return "CONJ:MISS_COMPOUND" # Missing conjunction in compound sentence # Check for missing conjunctions in complex sentences elif "complex" in original_text.lower(): return "CONJ:MISS_COMPLEX" # Missing conjunction in complex sentence # Check if the original text is empty (indicating something might be missing) if original_text.strip() == "": # Check if a coordinating conjunction is missing if any(word in coordinating_conjunctions for word in original_tokens_lower): return "CONJ:MISS_COORD" # Missing coordinating conjunction # Check if a subordinating conjunction is missing elif any(word in subordinating_conjunctions for word in original_tokens_lower): return "CONJ:MISS_SUBORD" # Missing subordinating conjunction # Check if it's a compound sentence missing a conjunction elif "compound" in " ".join(original_tokens_lower): return "CONJ:MISS_COMPOUND" # Missing conjunction in a compound sentence # If no conjunction error, return missing preposition error return "PREP:MISS" # Extra preposition detection (for determiners like "the") if any(tok.lower() == "the" for tok in original_tokens_lower): return "PREP:EXTRA" # Extra preposition # Verb infinitive error (e.g., missing "to") if any(word in original_word for word in verbs_followed_by_infinitives) and "to" not in original_word and "to" in corrected_word: return "VERB:INFINITIVE_ERROR" # Infinitive form required (e.g., "I want to go home") # Extra "to" error (when "to" should not be there) if "to" in original_word and "to" not in corrected_word: return "EXTRA:TO" # Extra "to" (e.g., "I want to go home") # Default to incorrect preposition if none of the above apply return "PREP:IN" # Incorrect preposition used # Handle adjective and adverb errors if error_type == "ADJ": original_word = original_text.strip(string.punctuation) corrected_word = corrected_text.strip(string.punctuation) doc_original = nlp(original_text) doc_corrected = nlp(corrected_text) # Get the lemmatized form (base form) of the adverbs original_lemma = " ".join([token.lemma_ for token in doc_original]) corrected_lemma = " ".join([token.lemma_ for token in doc_corrected]) # Check for comparative adverb error if corrected_text.endswith("er") and original_lemma != corrected_lemma: return "ADV:COMP" # Check for superlative adverb error if corrected_text.endswith("est") and original_lemma != corrected_lemma: return "ADV:SUP" # Check for comparative adjective error (e.g., "more smart" -> "smarter") if corrected_word.endswith("er"): return "ADJ:COMP" # Comparative adjective error # Check for superlative adjective error (e.g., "most tall" -> "tallest") if corrected_word.endswith("est"): return "ADJ:SUP" # Superlative adjective error elif corrected_text.lower() == "" and original_text.lower() == "more": return "ADJ:COMP" elif corrected_text.lower() == "more" and original_text.lower() == "more": return "ADJ:COMP" elif corrected_text.lower() == "" and original_text.lower() == "most": return "ADJ:SUP" elif corrected_text.lower() == "most" and original_text.lower() == "most": return "ADJ:SUP" # Check if the original word is an adjective and corrected word is an adverb if any(token.pos_ == "ADJ" for token in doc_original) and any(token.pos_ == "ADV" for token in doc_corrected): return "ADV:FORM_ERR" # Adjective used where adverb is required # General adjective form error else: return "ADJ:FORM" elif error_type == "ADV": original_word = original_text.strip(string.punctuation) corrected_word = corrected_text.strip(string.punctuation) doc_original = nlp(original_text) doc_corrected = nlp(corrected_text) # Get the lemmatized form (base form) of the adverbs original_lemma = " ".join([token.lemma_ for token in doc_original]) corrected_lemma = " ".join([token.lemma_ for token in doc_corrected]) original_tokens = original_text.lower().split() original_adverbs = [token.text.lower() for token in doc_original if token.pos_ == 'ADV'] corrected_adverbs = [token.text.lower() for token in doc_corrected if token.pos_ == 'ADV'] # Compare to find missing adverbs in the original text missing_adverbs = [adv for adv in corrected_adverbs if adv not in original_adverbs] # Check for coordinating conjunction errors if any(word in coordinating_conjunctions for word in original_tokens): return "CONJ:COORD" # Coordinating conjunction error # Check for subordinating conjunction errors elif any(word in subordinating_conjunctions for word in original_tokens): return "CONJ:SUBORD" # Subordinating conjunction error # Check for missing conjunctions in compound sentences elif "compound" in original_text.lower(): return "CONJ:MISS_COMPOUND" # Missing conjunction in compound sentence # Check for missing conjunctions in complex sentences elif "complex" in original_text.lower(): return "CONJ:MISS_COMPLEX" # Missing conjunction in complex sentence # Check for comparative adverb error if corrected_text.endswith("er") and original_lemma != corrected_lemma: return "ADV:COMP" # Check for superlative adverb error if corrected_text.endswith("est") and original_lemma != corrected_lemma: return "ADV:SUP" # Check for comparative adverb error (e.g., "more quickly" -> "quicker") if corrected_word.endswith("er"): return "ADV:COMP" # Comparative adverb error # Check for superlative adverb error (e.g., "most quickly" -> "quickest") if corrected_word.endswith("est"): return "ADV:SUP" # Superlative adverb error elif corrected_text.lower() == "" and original_text.lower() == "more": return "ADV:COMP" elif corrected_text.lower() == "most" and original_text.lower() == "more": return "ADV:COMP" elif corrected_text.lower() == "" and original_text.lower() == "most": return "ADV:SUP" elif corrected_text.lower() == "more" and original_text.lower() == "most": return "ADV:SUP" # Check if the original word is an adjective and corrected word is an adverb elif any(token.pos_ == "ADJ" for token in doc_original) and any(token.pos_ == "ADV" for token in doc_corrected): return "ADV:FORM_ERR" # Adjective used where adverb is required elif missing_adverbs: return "ADV:MISS" # General adverb form error else: return "ADV:FORM" # Conjunction errors elif error_type == "CONJ": original_tokens = original_text.lower().split() # Check for coordinating conjunction errors if any(word in coordinating_conjunctions for word in original_tokens): return "CONJ:COORD" # Coordinating conjunction error # Check for subordinating conjunction errors elif any(word in subordinating_conjunctions for word in original_tokens): return "CONJ:SUBORD" # Subordinating conjunction error # Check for missing conjunctions in compound sentences elif "compound" in original_text.lower(): return "CONJ:MISS_COMPOUND" # Missing conjunction in compound sentence # Check for missing conjunctions in complex sentences elif "complex" in original_text.lower(): return "CONJ:MISS_COMPLEX" # Missing conjunction in complex sentence else: return "CONJ:GEN" # General conjunction error # Punctuation errors elif error_type == "PUNCT": if "," in original_text and "." in corrected_text: return "PUNCT:COMMA_SPLICE" # Comma splice error elif ";" in original_text and "," in corrected_text: return "PUNCT:SEMICOLON" # Semicolon misuse elif original_text.endswith("."): return "PUNCT:PERIOD" # Missing period else: return "PUNCT:GEN" # General punctuation error # Orthography errors elif error_type == "ORTH": # Capitalization error: first letter only if original_text.istitle() and corrected_text.islower(): return "ORTH:CAP_FIRST" # Only first letter needs capitalization # Uppercase to lowercase in the entire word elif original_text.isupper() and corrected_text.islower(): return "ORTH:CAP" # Uppercase to lowercase # Lowercase to title case (first letter of each word should be capitalized) elif original_text.lower() == corrected_text.lower() and corrected_text.istitle(): return "ORTH:CAP_WORDS" # Correct capitalization for each word # Missing space between words elif re.search(r"[a-z][A-Z]", original_text): return "ORTH:SPACE" # Missing space between words else: return "ORTH:GEN" # General orthography error # Syntax errors elif error_type == "SPELL": if "-" not in original_text and "-" in corrected_text: # New check to see if corrected text adds a hyphen to the original original_no_hyphen = re.sub(r"-", "", corrected_text).lower() if original_no_hyphen == original_text.lower(): return "ORTH:HYP" # Missing hyphen in compound word else: return "SPELL" if error_type == "MORPH": # Strip punctuation for a cleaner comparison original_word = original_text.strip(string.punctuation) corrected_word = corrected_text.strip(string.punctuation) # Adverbial form error (when an adjective is incorrectly used as an adverb) if original_word.endswith("ful") and corrected_word.endswith("fully"): return "MORPH:ADJ_TO_ADV" # E.g., "careful" -> "carefully" elif original_word.endswith("ly") and not corrected_word.endswith("ly"): return "MORPH:ADV_TO_ADJ" # E.g., "quickly" -> "quick" # Comparative and superlative morphological errors elif original_word.startswith("more ") or original_word.startswith("most "): if not corrected_word.startswith("more ") and not corrected_word.startswith("most "): return "MORPH:COMP_SUPER" # Incorrect use of comparative/superlative, e.g., "more quicker" -> "quicker" # Spelling-derived morphology error (e.g., "advise" vs. "advice") elif original_word == "advise" and corrected_word == "advice": return "MORPH:SPELL_NOUN" # Spell-based noun error # General adjective-to-adverb error elif original_word.endswith("ly") != corrected_word.endswith("ly"): return "MORPH:ADJ_ADV_FORM" # Catch-all for adjective/adverb form issues if corrected_word.endswith("'s") and not original_word.endswith("'s"): return "NOUN:POS_MISS" elif original_word.endswith("'s") and not corrected_word.endswith("'s"): return "NOUN:POS_EXTRA" # Check for plural/singular errors (NOUN:NUM) elif original_word.endswith("s") and not corrected_word.endswith("s"): return "NOUN:SG" # Should be singular elif not original_word.endswith("s") and corrected_word.endswith("s"): return "NOUN:PL" # Should be plural # Check for countable/uncountable noun errors (NOUN:COUNT) elif original_word in non_count_nouns_list and not corrected_word in non_count_nouns_list: return "NOUN:COUNT" # Incorrect use of countable noun elif original_word.endswith("s") and corrected_word in non_count_nouns_list: return "NOUN:NUM_UNCOUNT" # Plural form of an uncountable noun # Check for possessive noun errors (NOUN:POS) elif corrected_word.endswith("'s") and not original_word.endswith("'s"): return "NOUN:POS_MISS" # Missing possessive elif original_word.endswith("'s") and not corrected_word.endswith("'s"): return "NOUN:POS_EXTRA" # Extra possessive # General morphology error (fallback) return "MORPH:GEN" # Generic morphology error if error_type == "OTHER": original_word = original_text.strip(string.punctuation) corrected_word = corrected_text.strip(string.punctuation) doc_original = nlp(original_text) doc_corrected = nlp(corrected_text) original_tokens_lower = [str(token).lower() for token in original_tokens] corrected_tokens_lower = [str(token).lower() for token in corrected_tokens] # Process each token to check for possessive errors for original_word, corrected_word in zip(original_tokens, corrected_tokens): # **Corrected Lines Below:** # Replace `original_word.endswith` with `original_word.text.endswith` # and `corrected_word.endswith` with `corrected_word.text.endswith` if corrected_word.text.endswith("'s") and not original_word.text.endswith("'s"): return "NOUN:POS_MISS" # Missing possessive apostrophe elif original_word.text.endswith("'s") and not corrected_word.text.endswith("'s"): return "NOUN:POS_EXTRA" # Extra possessive apostrophe # Check for plural/singular errors elif original_word.text.endswith("s") and not corrected_word.text.endswith("s"): return "NOUN:SG" # Should be singular elif not original_word.text.endswith("s") and corrected_word.text.endswith("s"): return "NOUN:PL" # Should be plural # Check for hyphenation issues specifically for compound adjectives if "-" in corrected_word.text and "-" not in original_word.text: return "ORTH:HYP" # Case where hyphen is added correctly in the corrected word elif "-" not in corrected_word.text and "-" in original_word.text: return "ORTH:HYP" # Case where hyphen was missing in the original word # Check for other morphological errors if any(token.pos_ == "ADJ" for token in doc_original) and any(token.pos_ == "ADV" for token in doc_corrected): return "ADV:FORM_ERR" # Adjective used where adverb is required if any(token.pos_ == "ADV" for token in doc_original) and any(token.pos_ == "ADJ" for token in doc_corrected): return "ADJ:FORM_ERR" # Adverb used where adjective is required # Check for possessive noun errors (NOUN:POS) if corrected_word.text.endswith("'s") and not original_word.text.endswith("'s"): return "NOUN:POS_MISS" # Missing possessive elif original_word.text.endswith("'s") and not corrected_word.text.endswith("'s"): return "NOUN:POS_EXTRA" # Extra possessive # Check for plural/singular errors, but only after possessive errors elif original_word.text.endswith("s") and not corrected_word.text.endswith("s"): return "NOUN:SG" # Should be singular elif not original_word.text.endswith("s") and corrected_word.text.endswith("s"): return "NOUN:PL" # Should be plural # Check for countable/uncountable noun errors (NOUN:COUNT) elif original_word.text in non_count_nouns_list and not corrected_word.text in non_count_nouns_list: return "NOUN:COUNT" # Incorrect use of countable noun # For possessive apostrophe errors specifically with words like "mother" elif original_word.text == "mother" and corrected_word.text == "mother's": return "NOUN:POS_MISS" # Missing possessive apostrophe # Handle hyphenation specifically for compound adjectives if "-" in corrected_text and "-" not in original_text: original_no_hyphen = re.sub(r"-", "", corrected_text).lower() if original_no_hyphen == original_text.lower(): return "ORTH:HYP" # Missing hyphen in compound word # Handle punctuation issues if ',' in original_text and ',' not in corrected_text: return "PUNCT:EXTRA_COMMA" elif ',' in corrected_text and ',' not in original_text: return "PUNCT:MISSING_COMMA" elif ':' in original_text and ':' not in corrected_text: return "PUNCT:EXTRA_COLON" elif ':' in corrected_text and ':' not in original_text: return "PUNCT:MISSING_COLON" elif ';' in original_text and ';' not in corrected_text: return "PUNCT:EXTRA_SEMICOLON" elif ';' in corrected_text and ';' not in original_text: return "PUNCT:MISSING_SEMICOLON" elif original_text.endswith('.') and not corrected_text.endswith('.'): return "PUNCT:EXTRA_PERIOD" elif corrected_text.endswith('.') and not original_text.endswith('.'): return "PUNCT:MISSING_PERIOD" # Check if a possessive pronoun in the original is replaced with another in the corrected text if any(token in possessive_pronouns for token in original_tokens) and any(token in possessive_pronouns for token in corrected_tokens): # Ensure the original and corrected tokens are not the same if set(original_tokens).intersection(possessive_pronouns) != set(corrected_tokens).intersection(possessive_pronouns): return "DET:POSSESSIVE_PRONOUN_SUBSTITUTION" else: return "OTHER" return "OTHER" # Default fallback def classify_comma_error(corrected_sentence, original_tokens, corrected_tokens): """ Classifies missing comma errors into SEQ, COMPOUND, and COMPLEX based on the presence of dependent and independent clauses. """ doc = nlp(corrected_sentence) # Check for MISSING:COMMA_COMPOUND - multiple independent clauses joined by a coordinating conjunction independent_clauses = [sent for sent in doc.sents if any(tok.dep_ == 'ROOT' for tok in sent)] if len(independent_clauses) > 1: for token in doc: # Look for a coordinating conjunction (cc) and check if the previous clause has no comma if token.dep_ == 'cc' and not any("," in tok.text for tok in doc[:token.i]): return "MISSING:COMMA_COMPOUND" # Missing comma before conjunction in compound sentence # Check for MISSING:COMMA_COMPLEX - dependent clause followed by an independent clause dependent_clause = any(token.dep_ == 'mark' for token in doc) # Subordinating conjunction independent_clause = any(token.dep_ == 'ROOT' for token in doc) if dependent_clause and independent_clause: for token in doc: if token.dep_ in {'advcl', 'csubj'} and not any("," in tok.text for tok in doc[:token.i]): return "MISSING:COMMA_COMPLEX" # Missing comma before dependent clause in complex sentence # Check for MISSING:COMMA_SEQ - sequence of items (nouns, adjectives) without proper commas sequence_items = [token for token in doc if token.dep_ in {'amod', 'attr', 'npadvmod', 'conj', 'appos'}] conjunctions = [token for token in doc if token.dep_ == 'cc'] # Detect a list structure but missing commas if len(sequence_items) > 1 and conjunctions and not any("," in token.text for token in sequence_items): return "MISSING:COMMA_SEQ" # If no specific type found, return general missing comma error return "MISSING:COMMA" def classify_error(edit, original_sentence, corrected_sentence): # Extract error information from the ERRANT edit object error_type = edit.type[2:] # Remove the operation code ('R:', 'M:', 'U:') orig_str = edit.o_str corr_str = edit.c_str orig_start = edit.o_start orig_end = edit.o_end corr_start = edit.c_start corr_end = edit.c_end original_tokens = nlp(orig_str) corrected_tokens = nlp(corr_str) sentence_tense = determine_tense(corrected_sentence) if error_type in ["VERB:TENSE", "VERB:SVA", "VERB:FORM"]: new_error_code = f"{error_type}_{sentence_tense}" else: new_error_code = map_error_code(error_type, original_sentence, original_tokens, corrected_tokens) return new_error_code def get_detailed_edits(original_sentence, corrected_sentence): # Process the sentences with spaCy orig_doc = nlp(original_sentence) cor_doc = nlp(corrected_sentence) # Use ERRANT to get the edits edits = annotator.annotate(orig_doc, cor_doc) error_types = [] for edit in edits: error_type = classify_error(edit, original_sentence, corrected_sentence) if error_type: orig_str = edit.o_str corr_str = edit.c_str error_types.append((error_type, orig_str, corr_str)) return error_types def sentence_length_analysis(doc): sentence_lengths = [len(sent.text.split()) for sent in doc.sents] short_sentences = sum(1 for length in sentence_lengths if length < 7) medium_sentences = sum(1 for length in sentence_lengths if 7 <= length <= 12) long_sentences = sum(1 for length in sentence_lengths if length > 12) return short_sentences, medium_sentences, long_sentences def detect_clauses_spacy(doc): independent_clauses = 0 dependent_clauses = 0 for sent in doc.sents: for token in sent: if token.dep_ == 'ROOT': independent_clauses += 1 elif token.dep_ in ['advcl', 'ccomp', 'xcomp', 'relcl']: dependent_clauses += 1 return independent_clauses, dependent_clauses def detect_noun_phrases(doc): noun_phrases = list(doc.noun_chunks) complex_phrases = sum(1 for np in noun_phrases if len(np.text.split()) > 3) # More than 3 words return len(noun_phrases), complex_phrases def generate_star_rating(score): """Generate star ratings based on score (out of 5)""" return "".join(["β˜…" if i < score else "β˜†" for i in range(5)]) def analyze_complexity(text): doc = nlp(text) # Sentence analysis short_sentences, medium_sentences, long_sentences = sentence_length_analysis(doc) # Clause detection (independent and dependent) independent_clauses, dependent_clauses = detect_clauses_spacy(doc) total_clauses = independent_clauses + dependent_clauses # Noun phrase complexity total_noun_phrases, complex_noun_phrases = detect_noun_phrases(doc) # Scoring based on adjusted rubric: if long_sentences >= 2 and total_clauses >= 5 and complex_noun_phrases >= 3: complexity_score = 5 complexity_feedback = "Your text shows excellent complexity with long sentences, varied clauses, and complex noun phrases." elif (long_sentences + medium_sentences) >= 3 and total_clauses >= 3 and complex_noun_phrases >= 2: complexity_score = 4 complexity_feedback = "Your text has a good variety of sentence structures, including some complex clauses." elif medium_sentences >= 2 and total_clauses >= 2: complexity_score = 3 complexity_feedback = "Your text has a moderate level of complexity with medium-length sentences and some clause variety." elif short_sentences >= 2 and total_clauses >= 1: complexity_score = 2 complexity_feedback = "Your text is relatively simple, with mostly short sentences and simple clauses." else: complexity_score = 1 complexity_feedback = "Your text has very short sentences with little to no variety, and noun phrases are very basic." # Complexity details as a dictionary complexity_details = { 'num_sentences': len(list(doc.sents)), 'short_sentences': short_sentences, 'medium_sentences': medium_sentences, 'long_sentences': long_sentences, 'independent_clauses': independent_clauses, 'dependent_clauses': dependent_clauses, 'total_noun_phrases': total_noun_phrases, 'complex_noun_phrases': complex_noun_phrases } return complexity_score, complexity_feedback, complexity_details def analyze_fluency(text): doc = nlp(text) sentences = list(doc.sents) num_sentences = len(sentences) num_words = len([token for token in doc if not token.is_punct and not token.is_space]) # Calculate sentence score if num_sentences >= 9: sentence_score = 5 elif num_sentences >= 7: sentence_score = 4 elif num_sentences >= 5: sentence_score = 3 elif num_sentences >= 2: sentence_score = 2 else: sentence_score = 1 # Calculate word count score if num_words >= 100: word_score = 5 elif num_words >= 61: word_score = 4 elif num_words >= 31: word_score = 3 elif num_words >= 15: word_score = 2 else: word_score = 1 # Combine scores fluency_score = min(sentence_score, word_score) # Fluency feedback feedback_options = { 5: "Your writing is very fluent with a substantial length and number of sentences.", 4: "Your writing is fluent with a good length and sentence count.", 3: "Your writing has moderate fluency with an average length.", 2: "Your writing is short with limited fluency.", 1: "Your writing is very short and lacks fluency." } fluency_feedback = feedback_options[fluency_score] # Fluency details as a dictionary fluency_details = { 'num_sentences': num_sentences, 'num_words': num_words } return fluency_score, fluency_feedback, fluency_details def analyze_accuracy(all_edits): """Analyze accuracy and return score, feedback, details, detailed_errors (tuples), and corrected text.""" error_count = 0 error_type_counts = {} # Process each edit from all_edits for edit in all_edits: error_type, original_string, corrected_string = edit # Get error details for each edit error_type_counts[error_type] = error_type_counts.get(error_type, 0) + 1 error_count += 1 # Scoring logic if error_count == 0: accuracy_score = 5 accuracy_feedback = "Your writing demonstrates mastery with minimal or no grammatical errors." elif error_count <= 3: accuracy_score = 4 accuracy_feedback = "Your writing shows competence with minor grammar, punctuation, or spelling errors." elif error_count <= 6: accuracy_score = 3 accuracy_feedback = "Your writing has several grammatical issues that need attention." elif error_count <= 10: accuracy_score = 2 accuracy_feedback = "Your writing contains numerous grammatical issues affecting readability." else: accuracy_score = 1 accuracy_feedback = "Your writing contains a significant number of grammatical problems." error_details = "\n".join([f"{etype}: {count}" for etype, count in error_type_counts.items()]) return accuracy_score, accuracy_feedback, error_details def convert_tuple_errors_to_dicts(detailed_errors): """ Convert the tuple-based detailed_errors into a list of dictionaries so they can be displayed by 'Detected Errors' code expecting .get(...) calls. Now uses error_code.json to fetch the full error name, explanation, and css_structure. """ # Load the error_code.json once here (adjust the path if needed). error_json_path = os.path.join(os.getcwd(), "data/error_code.json") with open(error_json_path, "r", encoding="utf-8") as f: error_data = json.load(f) # Build a lookup: e.g. {"VERB:INFL": {"full_error_name": "Verb Inflection Error", "explanation": "...", "css_structure": "..."}, ...} error_map = {} for item in error_data: code = item["error_code"] full_name = item["full_error_name"] expl = item["explanation"] css_struct = item.get("css_structure", "No additional information available.") error_map[code] = { "full_error_name": full_name, "explanation": expl, "css_structure": css_struct } dict_list = [] for idx, (error_type, orig_str, corr_str) in enumerate(detailed_errors): if error_type in error_map: error_info = error_map[error_type] full_error_name = error_info["full_error_name"] explanation = error_info["explanation"] css_structure = error_info["css_structure"] else: # Fallback if not in JSON full_error_name = error_type explanation = "No explanation found for this code." css_structure = "No additional information available." dict_list.append({ "id": idx, "full_error_name": full_error_name, "explanation": explanation, "css_structure": css_structure, "error_details": { "error_type": error_type, "orig_str": orig_str, "corr_str": corr_str } }) return dict_list def apply_highlight(text, detailed_errors): """ Apply red dashed borders to errors. Each error on hover shows a tooltip with the FULL ERROR NAME from `error_code.json`. `detailed_errors`: list of tuples (error_type, orig_str, corr_str). """ # 1) Load error_code.json once here error_json_path = os.path.join(os.getcwd(), "data/error_code.json") with open(error_json_path, "r", encoding="utf-8") as f: error_data = json.load(f) # 2) Build a map from "error_code" -> "full_error_name" error_map = {} for item in error_data: code = item["error_code"] full_name = item["full_error_name"] error_map[code] = full_name highlighted_text = text errors_applied = set() for error_type, orig_str, _ in detailed_errors: if orig_str in errors_applied: continue # Lookup full error name, fallback to the code if missing full_name = error_map.get(error_type, error_type) pattern = rf"\b{re.escape(orig_str)}\b" replacement = ( f"" f"{orig_str}" f"{full_name}" f"" ) highlighted_text, count = re.subn(pattern, replacement, highlighted_text, count=1) if count > 0: errors_applied.add(orig_str) return highlighted_text def analyze_user_input(user_input: str, grammar): """ Splits user input into sentences, calls grammar.correct() on each, collects edits and a highlighted version of the text, and builds a dictionary mapping error codes to the corresponding corrected sentence. Returns: final_highlighted (str): The HTML-highlighted text. all_edits (list of tuples): The list of edits for all sentences. final_corrected_text (str): The entire corrected text as a single string. type_and_sent (dict): A dictionary where the key is the error code and the value is the corrected sentence. """ # Split the input text into sentences splitted_sentences = re.split(r'(?<=[.!?])\s+', user_input.strip()) all_edits = [] sentences = [] type_and_sent = {} final_highlighted = "" for sent in splitted_sentences: if sent.strip(): # Correct the sentence corrected_sent = grammar.correct(sent) # Get detailed edits (assumed to be a list of tuples like (error_code, original, corrected)) sentence_edits = get_detailed_edits(sent, corrected_sent) all_edits.extend(sentence_edits) sentences.append(corrected_sent) # If there is at least one edit, update the dictionary mapping error code to sentence. if sentence_edits: error_code = sentence_edits[0][0] # Use the error code from the first edit # If the error code already exists, append the new sentence to the existing value. if error_code in type_and_sent: type_and_sent[error_code] += " " + corrected_sent else: type_and_sent[error_code] = corrected_sent # Apply highlighting to the original sentence highlighted = apply_highlight(sent, sentence_edits) final_highlighted += highlighted + " " # Combine all corrected sentences into a single text final_text = " ".join(sentences).strip() return final_highlighted.strip(), all_edits, final_text, type_and_sent def task_1_page(): st.title("Task Page") add_css() # --- 1) Initialize session state variables --- if 'task_selection' not in st.session_state: st.session_state['task_selection'] = list(tasks_dict.keys())[0] if 'limited_task_selection' not in st.session_state: # Initialize with the first task from the mapped subset initial_limited_tasks = TASK_MAPPING_TASK.get(st.session_state['task_selection'], [st.session_state['task_selection']]) st.session_state['limited_task_selection'] = initial_limited_tasks[0] if initial_limited_tasks else None if 'show_example' not in st.session_state: st.session_state['show_example'] = False if 'show_rule' not in st.session_state: st.session_state['show_rule'] = False if 'analysis_done' not in st.session_state: st.session_state['analysis_done'] = False if 'task_completed' not in st.session_state: st.session_state['task_completed'] = False # Use time.time() so that 'start_time' is stored as a float everywhere if 'start_time' not in st.session_state: st.session_state['start_time'] = time.time() # Track the maximum number of errors encountered across checks if 'max_errors' not in st.session_state: st.session_state['max_errors'] = 0 # --- 1) Initialize session state variables --- if 'task_selection' not in st.session_state: st.session_state['task_selection'] = list(tasks_dict.keys())[0] if 'limited_task_selection' not in st.session_state: # Initialize limited task selection based on the primary selection initial_limited_tasks = TASK_MAPPING_TASK.get(st.session_state['task_selection'], [st.session_state['task_selection']]) st.session_state['limited_task_selection'] = initial_limited_tasks[0] if initial_limited_tasks else None # --- 2) Set Primary Task Selection in the Background (Hidden) --- primary_selection = st.session_state['task_selection'] # --- 3) Get available tasks based on primary selection --- limited_tasks = TASK_MAPPING_TASK.get(primary_selection, [primary_selection]) # --- 4) Display the Limited Task Selection (ONLY ONE DROPDOWN VISIBLE) --- limited_selection = st.selectbox( "Available Tasks", options=limited_tasks, index=limited_tasks.index(st.session_state['limited_task_selection']) if st.session_state['limited_task_selection'] in limited_tasks else 0, key='limited_task_selection_box', # Unique key format_func=lambda x: TASK_TITLE_MAPPING.get(x, x) # Display task names properly ) # --- 5) Update session state if selection changes --- if limited_selection != st.session_state['limited_task_selection']: st.session_state['limited_task_selection'] = limited_selection st.session_state['show_example'] = False st.session_state['show_rule'] = False st.session_state['analysis_done'] = False st.session_state['task_completed'] = False st.session_state['start_time'] = time.time() # Reset timer st.session_state['max_errors'] = 0 # Reset error count # --- 6) Retrieve Selected Task Details --- selected_task = tasks_dict.get(st.session_state['limited_task_selection'], {}) task_key = st.session_state.get('limited_task_selection') if not task_key or task_key not in tasks_dict: selected_task = {"task": "⚠️ No task assigned or found!", "example": "No example available."} else: selected_task = tasks_dict[task_key] # 3) Display instructions col1, col2 = st.columns([4, 1]) with col1: st.markdown(f'''

πŸ“ Task Instructions:

{selected_task["task"]}

''', unsafe_allow_html=True) with col2: if st.button("πŸ“– See Example", key='see_example'): st.session_state['show_example'] = not st.session_state['show_example'] if st.button("πŸ“’ Rule", key='see_rule'): st.session_state['show_rule'] = not st.session_state['show_rule'] if st.session_state['show_example']: example_text = selected_task.get("example", "No example provided.") if example_text.lower() == "none": st.warning("No example available for this task.") else: st.markdown(f"""

Example:

{example_text}

""", unsafe_allow_html=True) if st.session_state['show_rule']: rule_id = TASK_RULE_MAPPING.get(st.session_state['task_selection']) if rule_id: rule_content = get_rule_content(rule_id) if rule_content: st.markdown(rule_content, unsafe_allow_html=True) else: st.warning("⚠️ No rule available for this task.") else: st.warning("⚠️ No rule mapping found for this task.") # 4) Two columns for user input and highlight input_col, highlight_col = st.columns([1, 1]) with input_col: user_input = st.text_area( "", height=200, placeholder="Start typing here...", key='user_input', label_visibility="collapsed" ) symbols_count = len(user_input) # Use float-based timing now = time.time() time_diff = now - st.session_state['start_time'] total_seconds = int(time_diff) minutes = total_seconds // 60 seconds = total_seconds % 60 time_spent_str = f"{minutes}m {seconds}s" metrics_col1, metrics_col2 = st.columns(2) with metrics_col1: st.markdown(f"""

πŸ•’ Time Spent:

{time_spent_str}

""", unsafe_allow_html=True) check_button_clicked = st.button("βœ… Check", key='check_button') if check_button_clicked: if user_input.strip(): try: st.session_state['user_input_task1'] = user_input final_highlighted, all_edits, corrected_text, type_and_sent = analyze_user_input(user_input, grammar) # Save to cumulative error types in session state if 'cumulative_errors' not in st.session_state: st.session_state['cumulative_errors'] = [] for edit in all_edits: st.session_state['cumulative_errors'].append({ "error_type": edit[0], "original_string": edit[1], "corrected_string": edit[2], }) st.session_state['detailed_errors'] = all_edits # Update analysis results st.session_state['max_errors'] = max(st.session_state.get('max_errors', 0), len(all_edits)) st.session_state['highlighted_text'] = final_highlighted.strip() accuracy_score, accuracy_feedback, accuracy_details = analyze_accuracy(all_edits) complexity_score, complexity_feedback, complexity_details = analyze_complexity(user_input) fluency_score, fluency_feedback, fluency_details = analyze_fluency(user_input) # If no edits, user is done st.session_state['task_completed'] = (len(all_edits) == 0) # Keep track of the largest number of errors found_errors = len(all_edits) st.session_state['max_errors'] = max(st.session_state['max_errors'], found_errors) # Save details errors_for_display = convert_tuple_errors_to_dicts(all_edits) st.session_state['analysis_done'] = True st.session_state['highlighted_text'] = final_highlighted.strip() st.session_state['corrected_text'] = corrected_text st.session_state['type_and_sent'] = type_and_sent st.session_state['detailed_errors'] = all_edits st.session_state['error_list'] = errors_for_display st.session_state['accuracy_score'] = accuracy_score st.session_state['accuracy_feedback'] = accuracy_feedback st.session_state['accuracy_details'] = accuracy_details st.session_state['complexity_score'] = complexity_score st.session_state['complexity_feedback'] = complexity_feedback st.session_state['complexity_details'] = complexity_details st.session_state['fluency_score'] = fluency_score st.session_state['fluency_feedback'] = fluency_feedback st.session_state['fluency_details'] = fluency_details st.session_state['user_input_task1'] = user_input except Exception as e: st.error(f"An error occurred during analysis: {e}") st.session_state['analysis_done'] = False else: st.error("Please enter some text to check.") with highlight_col: if st.session_state.get('analysis_done') and 'highlighted_text' in st.session_state: st.markdown( f"
{st.session_state['highlighted_text']}
", unsafe_allow_html=True ) # --- 5) If analysis done, show feedback columns --- if st.session_state.get('analysis_done'): feedback_col, errors_col = st.columns([1, 1]) with feedback_col: st.subheader("πŸ“ General Feedback") with st.expander("Expand to see more"): # Accuracy accuracy_points = st.session_state['accuracy_details'].split('\n') accuracy_list_items = ''.join(f"
  • {pt}
  • " for pt in accuracy_points) complexity_details = st.session_state['complexity_details'] complexity_list_items = ''.join([ f"
  • Number of sentences: {complexity_details['num_sentences']}
  • ", f"
  • Short sentences: {complexity_details['short_sentences']}
  • ", f"
  • Medium sentences: {complexity_details['medium_sentences']}
  • ", f"
  • Long sentences: {complexity_details['long_sentences']}
  • ", f"
  • Independent clauses: {complexity_details['independent_clauses']}
  • ", f"
  • Dependent clauses: {complexity_details['dependent_clauses']}
  • ", f"
  • Total noun phrases: {complexity_details['total_noun_phrases']}
  • ", f"
  • Complex noun phrases: {complexity_details['complex_noun_phrases']}
  • ", ]) fluency_details = st.session_state['fluency_details'] fluency_list_items = ''.join([ f"
  • Total sentences: {fluency_details['num_sentences']}
  • ", f"
  • Total word count: {fluency_details['num_words']}
  • ", ]) st.markdown(f"""

    Accuracy

    {generate_star_rating(st.session_state['accuracy_score'])}

    {st.session_state['accuracy_feedback']}

    More details

    Complexity

    {generate_star_rating(st.session_state['complexity_score'])}

    {st.session_state['complexity_feedback']}

    More details

    Fluency

    {generate_star_rating(st.session_state['fluency_score'])}

    {st.session_state['fluency_feedback']}

    More details
    """, unsafe_allow_html=True) st.markdown(f"""

    πŸ“š How Can This Help Improve My Writing?

    """, unsafe_allow_html=True) with errors_col: error_list = st.session_state.get('error_list', []) if error_list: st.subheader("🚨 Detected Errors") def go_to_learn_more(error_details): st.session_state['selected_error'] = error_details st.session_state.page = 'learn_more' for idx, error in enumerate(error_list): error_id = error.get('id', idx) full_error_name = error.get('full_error_name', 'Unknown error') explanation = error.get('explanation', 'No explanation provided') error_details = error.get('error_details', {}) st.markdown(f"""

    Error {error_id + 1}: {full_error_name}

    {explanation}

    """, unsafe_allow_html=True) underlying_type = error_details.get('error_type', '') if underlying_type == "OTHER": full_corrected_sentence = st.session_state.get('corrected_text', '') type_and_sent = st.session_state.get('type_and_sent', {}) full_corrected_sentence = type_and_sent.get(underlying_type, "Sentence not found") if full_corrected_sentence: st.info(f"βœ… The correct sentence is: **{full_corrected_sentence}**") else: st.warning("No full corrected sentence available.") else: if st.button( f"πŸ“– Learn more about Error {error_id + 1}", key=f"learn_more_{error_id}", on_click=go_to_learn_more, args=(error_details,), help="Click to learn more about this error." ): pass st.markdown("
    ", unsafe_allow_html=True) else: st.subheader("πŸŽ‰ No Grammar Errors Found!") st.markdown("""

    Your writing looks good! No grammar errors were detected.

    """, unsafe_allow_html=True) # If the task is completed (no errors left), show "Practice" + "Next" current_task = st.session_state['task_selection'] if st.session_state['task_completed']: st.markdown("---") col_practice, col_next = st.columns(2) with col_practice: if st.button("Practice", key=f'{current_task}_practice'): navigate_to_practice() with col_next: if st.button("Next", key=f'{current_task}_next'): # a) Compute the time spent (float-based) end_time = time.time() time_diff = end_time - st.session_state['start_time'] total_seconds = int(time_diff) hh, remainder = divmod(total_seconds, 3600) mm, ss = divmod(remainder, 60) # Convert the total seconds to a time object if valid try: # Using datetime.time(...) from the datetime module time_obj = datetime.time(hh, mm, ss) except ValueError: time_obj = None # b) Store the maximum number of errors encountered max_errs = st.session_state['max_errors'] errors_info = {"max_error_count": max_errs} # c) Insert record to DB try: student_id = st.session_state.user_info.id task_id = current_task record_task_1_done( student_id=student_id, task_id=task_id, date=datetime.datetime.now(), # store current date/time time_spent=time_obj, errors_dict=errors_info, full_text=st.session_state.get('user_input_task1', "") ) except Exception as e: st.error(f"Error recording task: {e}") return # d) Reset everything st.session_state['start_time'] = time.time() # Reset again as float st.session_state['max_errors'] = 0 # e) Navigate next_page = task_to_next_page.get(current_task, 'default_page') navigate_to_page(next_page) # Additional CSS st.markdown(""" """, unsafe_allow_html=True) # Mapping from primary tasks to their limited subsets TASK_MAPPING_TASK = { "L1_T1_TAS_1": ["L1_T1_TAS_1"], "L1_T2_TAS_1": ["L1_T2_TAS_1", "L1_T2_TAS_2"], "L1_T3_TAS_1": ["L1_T3_TAS_1", "L1_T3_TAS_2", "L1_T3_TAS_3"], "L1_T4_TAS_1": ["L1_T4_TAS_1", "L1_T4_TAS_2"], "L1_T5_TAS_1": ["L1_T5_TAS_1", "L1_T5_TAS_2","L1_T5_TAS_3"], "L1_T6_TAS_1": ["L1_T6_TAS_1", "L1_T6_TAS_2", "L1_T6_TAS_3"], "L1_T7_TAS_1": ["L1_T7_TAS_1", "L1_T7_TAS_2"], "L1_T8_TAS_1": ["L1_T8_TAS_1", "L1_T8_TAS_2", "L1_T8_TAS_3"], "L1_T9_TAS_1": ["L1_T9_TAS_1","L1_T9_TAS_2", "L1_T9_TAS_3"], "L1_T10_TAS_1": ["L1_T10_TAS_1"], "L2_T1_TAS_1": ["L2_T1_TAS_1"], "L2_T2_TAS_1": ["L2_T2_TAS_1", "L2_T2_TAS_2", "L2_T2_TAS_3"], "L2_T3_TAS_1": ["L2_T3_TAS_1", "L2_T3_TAS_2","L2_T3_TAS_3"], "L2_T4_TAS_1": ["L2_T4_TAS_1", "L2_T4_TAS_2", "L2_T4_TAS_3"], "L2_T5_TAS_1": ["L2_T5_TAS_1", "L2_T5_TAS_2","L2_T5_TAS_3"], "L2_T6_TAS_1": ["L2_T6_TAS_1", "L2_T6_TAS_2"], "L2_T7_TAS_1": ["L2_T7_TAS_1", "L2_T7_TAS_2","L2_T7_TAS_3"], "L2_T8_TAS_1": ["L2_T8_TAS_1", "L2_T8_TAS_2","L2_T8_TAS_3"], "L2_T9_TAS_1": ["L2_T9_TAS_1", "L2_T9_TAS_2"], "L2_T5_TAS_1": ["L2_T10_TAS_1"], "L3_T1_TAS_1": ["L3_T1_TAS_1"], "L3_T2_TAS_1": ["L3_T2_TAS_1", "L3_T2_TAS_2","L3_T2_TAS_3"], "L3_T3_TAS_1": ["L3_T3_TAS_1", "L3_T3_TAS_2","L3_T3_TAS_3"], "L3_T4_TAS_1": ["L3_T4_TAS_1", "L3_T4_TAS_2"], "L3_T5_TAS_1": ["L3_T5_TAS_1", "L3_T5_TAS_2","L3_T5_TAS_3"], "L3_T6_TAS_1": ["L3_T6_TAS_1", "L3_T6_TAS_2","L3_T6_TAS_3"], "L3_T7_TAS_1": ["L3_T7_TAS_1", "L3_T7_TAS_2","L3_T7_TAS_3"], "L3_T8_TAS_1": ["L3_T8_TAS_1", "L3_T8_TAS_2"], "L3_T9_TAS_1": ["L3_T9_TAS_1", "L3_T9_TAS_2"], "L3_T10_TAS_1": ["L3_T10_TAS_1"], } def record_task_1_done(student_id, task_id, date, time_spent, errors_dict=None, full_text=None): """ Insert the finished task into the database, including full user input text. """ try: errors_info = errors_dict or {} # Add cumulative errors from session state if needed errors_info['error_types'] = st.session_state.get('cumulative_errors', []) if 'max_errors' in st.session_state: errors_info['max_error_count'] = st.session_state['max_errors'] complexity_details = st.session_state.get('complexity_details', {}) fluency_details = st.session_state.get('fluency_details', {}) # Create and insert the task record task_done_entry = TasksDone( student_id=student_id, task_id=task_id, date=date, time=time_spent, errors=errors_info, complexity=complexity_details, fluency=fluency_details, full_text={"text": full_text} if full_text else None # Store full text as JSON ) session.add(task_done_entry) session.commit() st.success(f"Task '{task_id}' recorded successfully.") except Exception as e: session.rollback() st.error(f"Failed to record task: {e}") def compute_scores_and_store_in_session(user_input: str, all_edits: list, corrected_text): """ Runs accuracy, complexity, fluency analyses on the entire user_input. Stores the results (scores, feedback, details, etc.) in st.session_state. """ accuracy_score, accuracy_feedback, accuracy_details = analyze_accuracy(all_edits) complexity_score, complexity_feedback, complexity_details = analyze_complexity(user_input) fluency_score, fluency_feedback, fluency_details = analyze_fluency(user_input) # Convert the multi-sentence edits to dict errors_for_display = convert_tuple_errors_to_dicts(all_edits) # Store main analysis flags st.session_state['analysis_done'] = True st.session_state['highlighted_text'] = st.session_state.get('highlighted_text', '') st.session_state['corrected_text'] = corrected_text st.session_state['detailed_errors'] = all_edits st.session_state['error_list'] = errors_for_display # Store scores/feedback st.session_state['accuracy_score'] = accuracy_score st.session_state['accuracy_feedback'] = accuracy_feedback st.session_state['accuracy_details'] = accuracy_details st.session_state['complexity_score'] = complexity_score st.session_state['complexity_feedback'] = complexity_feedback st.session_state['complexity_details'] = complexity_details st.session_state['fluency_score'] = fluency_score st.session_state['fluency_feedback'] = fluency_feedback st.session_state['fluency_details'] = fluency_details def display_feedback_and_errors(): """ Displays the feedback columns (Accuracy, Complexity, Fluency) and the detected errors in two columns side-by-side. Also shows the full corrected sentence after analysis. """ feedback_col, errors_col = st.columns([1, 1]) # === Feedback Section === with feedback_col: st.subheader("πŸ“ General Feedback") with st.expander("Expand to see more"): # Accuracy accuracy_points = st.session_state['accuracy_details'].split('\n') accuracy_list_items = ''.join(f"
  • {pt}
  • " for pt in accuracy_points) # Complexity complexity_details = st.session_state['complexity_details'] complexity_list_items = ''.join([ f"
  • Number of sentences: {complexity_details['num_sentences']}
  • ", f"
  • Short sentences: {complexity_details['short_sentences']}
  • ", f"
  • Medium sentences: {complexity_details['medium_sentences']}
  • ", f"
  • Long sentences: {complexity_details['long_sentences']}
  • ", f"
  • Independent clauses: {complexity_details['independent_clauses']}
  • ", f"
  • Dependent clauses: {complexity_details['dependent_clauses']}
  • ", f"
  • Total noun phrases: {complexity_details['total_noun_phrases']}
  • ", f"
  • Complex noun phrases: {complexity_details['complex_noun_phrases']}
  • ", ]) # Fluency fluency_details = st.session_state['fluency_details'] fluency_list_items = ''.join([ f"
  • Total sentences: {fluency_details['num_sentences']}
  • ", f"
  • Total word count: {fluency_details['num_words']}
  • ", ]) st.markdown(f"""

    Accuracy

    {generate_star_rating(st.session_state['accuracy_score'])}

    {st.session_state['accuracy_feedback']}

    More details

    Complexity

    {generate_star_rating(st.session_state['complexity_score'])}

    {st.session_state['complexity_feedback']}

    More details

    Fluency

    {generate_star_rating(st.session_state['fluency_score'])}

    {st.session_state['fluency_feedback']}

    More details
    """, unsafe_allow_html=True) # === Errors Section === with errors_col: error_list = st.session_state.get('error_list', []) if error_list: st.subheader("🚨 Detected Errors") for idx, error in enumerate(error_list): full_error_name = error.get('full_error_name', 'Unknown error') explanation = error.get('explanation', 'No explanation provided') css_structure = error.get('css_structure', 'No additional information available.') # Container for each error st.markdown(f"""

    Error {idx + 1}: {full_error_name}

    {explanation}

    """, unsafe_allow_html=True) # "Learn more" expander for CSS Structure with st.expander("πŸ“š Learn More", expanded=False): st.markdown(f"

    {css_structure}

    ", unsafe_allow_html=True) st.markdown("
    ", unsafe_allow_html=True) else: st.subheader("πŸŽ‰ No Grammar Errors Found!") st.markdown("""

    Your writing looks good! No grammar errors were detected.

    """, unsafe_allow_html=True) # === NEW: "Correct Sentence" Expander === corrected_text = st.session_state.get('corrected_text', '') if corrected_text: # Only show if corrected text exists with st.expander("Corrected Text", expanded=False): st.markdown(f"""

    {corrected_text}

    """, unsafe_allow_html=True) # Optional: CSS for error highlighting, if you use tooltips st.markdown(""" """, unsafe_allow_html=True) def mode_1_page(): """ Displays the Mode 1 page with task selection and analysis, and records data to the database once the user finishes. """ st.title("Task Page (Mode 1)") add_css() # Your existing CSS injection # 1) Initialize session state if 'task_selection' not in st.session_state: st.session_state['task_selection'] = list(tasks_dict.keys())[0] if 'analysis_done' not in st.session_state: st.session_state['analysis_done'] = False if 'start_time' not in st.session_state: st.session_state['start_time'] = time.time() # Float-based timer if 'error_list' not in st.session_state: st.session_state['error_list'] = [] # Store errors if 'final_user_input' not in st.session_state: st.session_state['final_user_input'] = "" # 2) Task selection title_to_id = {title: task_id for task_id, title in TASK_TITLE_MAPPING.items()} task_titles = list(TASK_TITLE_MAPPING.values()) current_task_id = st.session_state['task_selection'] current_task_title = TASK_TITLE_MAPPING.get(current_task_id, "Unknown Task") task_selection = st.selectbox( "Select Task", options=task_titles, index=task_titles.index(current_task_title) if current_task_title in task_titles else 0 ) selected_task_id = title_to_id.get(task_selection, current_task_id) if selected_task_id != st.session_state['task_selection']: st.session_state['task_selection'] = selected_task_id st.session_state['analysis_done'] = False st.session_state['start_time'] = time.time() st.session_state['error_list'] = [] # Reset errors # 3) Fetch and show the actual task instructions selected_task = tasks_dict[st.session_state['task_selection']] st.markdown(f'''

    πŸ“ Task Instructions:

    {selected_task["task"]}

    ''', unsafe_allow_html=True) # 4) Two columns: user input (left) + highlighted text (right) input_col, highlight_col = st.columns([1, 1]) with input_col: user_input = st.text_area( "", height=200, placeholder="Start typing here...", key='user_input', label_visibility="collapsed" ) # Store full user input in session state for database storage st.session_state['final_user_input'] = user_input # Calculate time spent current_time = time.time() time_spent_seconds = int(current_time - st.session_state['start_time']) minutes = time_spent_seconds // 60 seconds = time_spent_seconds % 60 time_spent_str = f"{minutes}m {seconds}s" # Display only "πŸ•’ Time Spent" (No symbols count) st.markdown(f"""

    πŸ•’ Time Spent:

    {time_spent_str}

    """, unsafe_allow_html=True) # "Check" button => Analyze text if st.button("βœ… Check", key='check_button'): if user_input.strip(): try: # (A) Analyze user input => returns (highlighted_text, edits_list) final_highlighted, all_edits, corrected_text = analyze_user_input(user_input, grammar) # (B) Store error types for database st.session_state['error_list'] = convert_tuple_errors_to_dicts(all_edits) # (C) Store highlighted text st.session_state['highlighted_text'] = final_highlighted # (D) Compute & store accuracy/complexity/fluency compute_scores_and_store_in_session(user_input, all_edits) st.session_state['corrected_text'] = corrected_text # Mark analysis as done st.session_state['analysis_done'] = True except Exception as e: st.error(f"An error occurred during analysis: {e}") st.session_state['analysis_done'] = False else: st.error("Please enter some text to check.") # Display highlighted text in the right column (Aligned Properly) with highlight_col: if st.session_state.get('analysis_done') and 'highlighted_text' in st.session_state: st.markdown( f"
    {st.session_state['highlighted_text']}
    ", unsafe_allow_html=True ) # 5) If analysis is done => show feedback & errors if st.session_state.get('analysis_done'): # Show your custom feedback UI display_feedback_and_errors() # "Submit my writing" => Saves to DB if st.button("Submit my writing"): try: # (A) Compute total time end_time = time.time() total_seconds = int(end_time - st.session_state['start_time']) hh, remainder = divmod(total_seconds, 3600) mm, ss = divmod(remainder, 60) # Create datetime.time(...) object if valid try: time_spent_obj = datetime.time(hh, mm, ss) except ValueError: time_spent_obj = None # (B) Get student ID student_id = st.session_state.user_info.id # (C) Get errors & details errors_dict = { "error_types": st.session_state.get('error_list', []) } # (D) Insert into database with full user input record_task_1_done( student_id=student_id, task_id=st.session_state['task_selection'], date=datetime.datetime.now(), # store exact timestamp time_spent=time_spent_obj, errors_dict=errors_dict, full_text=st.session_state['final_user_input'] # Store full user input ) # (E) Reset session for next attempt st.session_state['analysis_done'] = False st.session_state['start_time'] = time.time() st.session_state['error_list'] = [] st.success("Mode 1 task recorded successfully!") except Exception as e: st.error(f"Error recording Mode 1 task: {e}") # 6) Additional CSS (optional) st.markdown(""" """, unsafe_allow_html=True) def go_back_to_previous_page(): current_task = st.session_state.get('task_selection', '') # Fallback if not found target_page = TASK_BACK_MAPPING.get(current_task, 'default_page') st.session_state['page'] = target_page def go_back_to_task_page(): # Set the page back to your main task page's label (e.g. 'task_1' or 'task_1_page') st.session_state.page = 'task_1' # Optionally clear the selected error if you want to reset it st.session_state.pop('selected_error', None) def learn_more_page(): st.title("🎯 Learn more ") selected_error = st.session_state.get('selected_error') if not selected_error: st.error("No error selected. Please go back and select an error.") return # Extract basic fields from selected_error error_code = selected_error.get('error_type', '') orig_str = selected_error.get('orig_str', '') corr_str = selected_error.get('corr_str', '') # 1) Attempt to get original_sentence and corrected_sentence directly original_sentence = selected_error.get('original_sentence') corrected_sentence = selected_error.get('corrected_sentence') # 2) Fallback if missing if not original_sentence: # Maybe fallback to user's raw input from task_1_page fallback_original = st.session_state.get('user_input_task1') if fallback_original: original_sentence = fallback_original else: original_sentence = "[No original sentence found]" st.warning("No 'original_sentence' found in selected_error or session_state.") if not corrected_sentence: # Maybe fallback to the grammar-corrected text fallback_corrected = st.session_state.get('corrected_text') if fallback_corrected: corrected_sentence = fallback_corrected else: corrected_sentence = "[No corrected sentence found]" st.warning("No 'corrected_sentence' found in selected_error or session_state.") # Now that we have guaranteed *some* string, proceed # Get the full error name and explanation from your mapping error_info = error_code_mapping.get(error_code, {'full_error_name': error_code, 'explanation': ''}) full_error_name = error_info['full_error_name'] explanation = error_info['explanation'] st.write(f"{explanation}") type_and_sent = st.session_state.get('type_and_sent', {}) # Call the appropriate handler based on error_code if error_code == "SPELL": misspelled_word = orig_str correct_word = corr_str task_html = handle_spell_error(misspelled_word, correct_word) st.components.v1.html(task_html, height=500) elif error_code in punctuation_errors: # Ensure 'punctuation_errors' is defined sentence = type_and_sent.get(error_code, "Sentence not found") task_html = handle_punctuation_error(sentence) st.components.v1.html(task_html, height=500) elif error_code in VERB_ERROR_CODES: # Ensure 'VERB_ERROR_CODES' is defined incorrect_verb = orig_str correct_verb = corr_str sentence = type_and_sent.get(error_code, "Sentence not found") task_html = handle_verb_error(sentence, incorrect_verb, correct_verb) st.components.v1.html(task_html, height=500) elif error_code in NOUN_ERRORS: # Ensure 'NOUN_ERRORS' is defined incorrect_noun = orig_str correct_noun = corr_str sentence = type_and_sent.get(error_code, "Sentence not found") task_html = handle_noun_error(sentence, incorrect_noun, correct_noun) st.components.v1.html(task_html, height=500) elif error_code in ERROR_CODES_WITH_DISTRACTIONS: # Ensure 'ERROR_CODES_WITH_DISTRACTIONS' is defined sentence = type_and_sent.get(error_code, "Sentence not found") task_html = handle_distraction_error(sentence) st.components.v1.html(task_html, height=500) elif error_code in ERROR_CODES_SCRAMBLE_POS: # Ensure 'ERROR_CODES_SCRAMBLE_POS' is defined sentence = type_and_sent.get(error_code, "Sentence not found") task_html = handle_scramble_pos_error(sentence) st.components.v1.html(task_html, height=500) else: st.write("No specific task for this error type.") if st.button("πŸ”™ Back to Task Page"): go_back_to_task_page() st.rerun() # Function to generate color-coded HTML for POS tags def color_code_pos(word, pos): if pos == "PRON" or pos == "NOUN": return f'
    {word}
    ' elif pos == "VERB": return f'
    {word}
    ' elif pos == "ADJ": return f'
    {word}
    ' elif pos == "ADV": return f'
    {word}
    ' elif pos == "DET": return f'
    {word}
    ' elif pos == "ADP": return f'
    {word}
    ' elif pos in {"CCONJ", "SCONJ"}: return f'
    {word}
    ' else: return f'
    {word}
    ' # Mock function to detect errors and randomly assign error codes def detect_errors(sentence): words = sentence.split() error_codes = [ "WO", "PREP:MISS", "DET:MISS", "VERB:MISS", "VERB:FORM_OTHER", "OTHER", "NOUN", "EXTRA:TO", "DET:EXTRA", "PREP:EXTRA", "MORPH:GEN", "CONJ:GEN", "CONJ:MISS_COMPOUND", "CONJ:MISS_COMPLEX", "CONJ:MISS_COORD", "CONJ:MISS_SUBORD", "SPELL" ] return [(word, random.choice(error_codes)) for word in words] # Function to create a SPELL task with draggable letters def handle_spell_error (misspelled_word, correct_word): scrambled_letters = random.sample(list(correct_word), len(correct_word)) scrambled_html = ''.join([f'
    {letter}
    ' for letter in scrambled_letters]) return f"""

    Unscramble the letters to correct the misspelled word:

    {scrambled_html}

    """ def handle_punctuation_error(corrected_sentence): SUB_CONJS = { "although", "because", "if", "since", "though", "unless", "until", "whereas", "whether", "while", "when", "as", "even though" } COORD_CONJS = { "and", "but", "or", "nor", "so", "yet", "for" } doc = nlp(corrected_sentence) elements = [] main_clauses = [] subordinate_clauses = [] current_main_clause = "" current_sub_clause = "" is_subordinate = False def finalize_sub_clause(): nonlocal current_sub_clause, subordinate_clauses if current_sub_clause.strip(): subordinate_clauses.append(current_sub_clause.strip()) current_sub_clause = "" def finalize_main_clause(): nonlocal current_main_clause, main_clauses if current_main_clause.strip(): main_clauses.append(current_main_clause.strip()) current_main_clause = "" def finalize_current_clause(): if is_subordinate: finalize_sub_clause() else: finalize_main_clause() for token in doc: text_lower = token.text.lower() if token.is_punct: elements.append( f'
    {token.text}
    ' ) if token.text == "," and is_subordinate: finalize_sub_clause() is_subordinate = False continue if text_lower in COORD_CONJS and token.pos_ == "CCONJ": finalize_current_clause() is_subordinate = False elements.append( f'
    {token.text}
    ' ) continue if text_lower in SUB_CONJS and token.pos_ in {"SCONJ", "ADP"}: finalize_current_clause() is_subordinate = True elements.append( f'
    {token.text}
    ' ) continue if is_subordinate: if token.dep_ == "nsubj": current_sub_clause += f'{token.text} ' elif token.pos_ in {"VERB", "AUX"}: current_sub_clause += f'{token.text} ' else: current_sub_clause += f'{token.text} ' else: if token.dep_ == "nsubj": current_main_clause += f'{token.text} ' elif token.pos_ in {"VERB", "AUX"}: current_main_clause += f'{token.text} ' else: current_main_clause += f'{token.text} ' finalize_current_clause() for mc in main_clauses: elements.append( f'
    {mc}
    ' ) for sc in subordinate_clauses: elements.append( f'
    {sc}
    ' ) random.shuffle(elements) scrambled_html = "".join(elements) # Build the correct answer string with proper spacing and punctuation correct_answer = "" for token in doc: if token.is_punct: correct_answer += token.text elif token.i > 0 and not doc[token.i - 1].is_punct: correct_answer += " " + token.text else: correct_answer += token.text correct_answer_js = json.dumps(correct_answer.strip()) # Updated drag-and-drop HTML/JS drag_and_drop_html = f"""

    Arrange the clauses, conjunctions, and punctuation to form the correct sentence:

    {scrambled_html}

    """ return drag_and_drop_html def handle_noun_error(corrected_sentence, incorrect_noun, correct_noun): # Parse the sentence to identify parts of speech and mark noun phrases doc = nlp(corrected_sentence) elements = [] # Identify noun phrase dependencies and group relevant tokens noun_phrase = [] for token in doc: if token.text.lower() == correct_noun.lower(): # Replace the correct noun with the drop zone within the noun phrase noun_phrase.append('
    ___
    ') elif token.dep_ in {"nsubj", "dobj"} and token.pos_ == "NOUN": # Mark other parts of the noun phrase noun_phrase.append(f'{token.text} ') elif token.dep_ in {"det", "amod", "nummod"}: # Include determiners, adjectives, and number modifiers noun_phrase.append(f'{token.text} ') else: if noun_phrase: elements.append(f'{"".join(noun_phrase)}') noun_phrase = [] # Reset noun phrase list elements.append(f'{token.text} ') if noun_phrase: elements.append(f'{"".join(noun_phrase)}') noun_options = [ f'
    {incorrect_noun}
    ', f'
    {correct_noun}
    ' ] drag_and_drop_html = f"""

    🎯 Practice: Correct the Noun

    Drag and drop the correct noun into the blank space to complete the sentence.

    {" ".join(elements)}
    {" ".join(noun_options)}

    """ return drag_and_drop_html def handle_verb_error(corrected_sentence, incorrect_verb, correct_verb): """ Generates an HTML drag-and-drop interface for correcting verbs in a sentence, using the same checking logic as handle_noun_error. """ doc = nlp(corrected_sentence) elements = [] # Go through each token in the corrected sentence for token in doc: # If this is the correct verb to be replaced, insert the drop zone if token.text.lower() == correct_verb.lower() and token.pos_ in {"VERB", "AUX"}: elements.append('
    ___
    ') else: # Everything else, just add the token text elements.append(f'{token.text} ') # Create the draggable options for the user verb_options = [ f'
    {incorrect_verb}
    ', f'
    {correct_verb}
    ' ] # Construct the final HTML drag_and_drop_html = f"""

    🎯 Practice: Correct the Verb

    Drag and drop the correct verb into the blank space to complete the sentence.

    {" ".join(elements)}
    {" ".join(verb_options)}

    """ return drag_and_drop_html def clean_text_for_comparison(text): """ Cleans text by removing punctuation, converting to lowercase, and normalizing spaces for comparison purposes only. """ return re.sub(r'[^\w\s]', '', text).lower().strip() def handle_distraction_error(sentence): selected_error = st.session_state.get('selected_error') if not selected_error: st.error("No error selected. Please go back and select an error.") return # 1) Get corrected sentence corrected_sentence = selected_error.get('corrected_sentence', '').strip() if not corrected_sentence or corrected_sentence.lower() == 'n/a': corrected_sentence = sentence if not corrected_sentence: st.error("No valid corrected sentence found.") return # 2) Tokenize corrected sentence (exclude punctuation tokens) corrected_doc = nlp(corrected_sentence) corrected_words = [token.text for token in corrected_doc if token.is_alpha or token.is_digit] # Retrieve erroneous & corrected words erroneous_word = selected_error.get('orig_str', '').strip() corrected_word = selected_error.get('corr_str', '').strip() # Build task words for display (retain original case, exclude punctuation) task_words = corrected_words + [erroneous_word] task_words = [w for w in task_words if w and w.lower() != 'n/a'] # If the list is now empty or only one word, there's nothing to shuffle if len(task_words) < 2: st.warning("Not enough valid words to create a drag-and-drop exercise.") return # 3) Generate scrambled HTML for words scrambled_words_html = ''.join( f'
    {word}
    ' for word in random.sample(task_words, len(task_words)) ) # Sanitize correct answer for JavaScript comparison sanitized_correct_answer = clean_text_for_comparison(corrected_sentence) # Sanitized for comparison correct_answer_js = json.dumps(sanitized_correct_answer) # 4) Generate drag-and-drop HTML and JS drag_and_drop_html = f"""

    Arrange the words to form the correct sentence:

    {scrambled_words_html}

    """ return drag_and_drop_html def handle_scramble_pos_error(sentence): selected_error = st.session_state.get('selected_error') if not selected_error: st.error("No error selected. Please go back and select an error.") return # Try to get corrected_sentence from selected_error first corrected_sentence = selected_error.get('corrected_sentence') if not corrected_sentence: # Fallback: use st.session_state['corrected_text'], if present corrected_sentence = sentence if not corrected_sentence: st.error("No corrected sentence found in selected_error or session_state.") return # Now corrected_sentence is guaranteed to have a value corrected_doc = nlp(corrected_sentence) # Example: color_code_pos is presumably a function you already defined words_html = ''.join([color_code_pos(token.text, token.pos_) for token in corrected_doc]) # Scramble the words for the drag-and-drop task words_list = words_html.split('') scrambled_words = random.sample(words_list, len(words_list)) scrambled_words_html = ''.join([word + '' for word in scrambled_words if word]) correct_answer = ' '.join(token.text for token in corrected_doc) correct_answer_js = json.dumps(correct_answer) # The rest of your drag-and-drop HTML/JS remains the same: drag_and_drop_html = f"""

    Arrange the words to form the correct sentence:

    {scrambled_words_html}

    """ return drag_and_drop_html def add_css(): st.markdown(""" """, unsafe_allow_html=True) TASK_TITLE_MAPPING = { # Level 1 Tasks "L1_T1_TAS_1": "Level 1 - Study Location", "L1_T2_TAS_1": "Level 1 - New Routine", "L1_T2_TAS_2": "Level 1 - Sport Activities", "L1_T3_TAS_1": "Level 1 - New Place", "L1_T3_TAS_2": "Level 1 - Advertising Text", "L1_T3_TAS_3": "Level 1 - Tech Benefits", "L1_T4_TAS_1": "Level 1 - Trip Planning", "L1_T4_TAS_2": "Level 1 - Exhibition Guide", "L1_T5_TAS_1": "Level 1 - Movie Disagreement", "L1_T5_TAS_2": "Level 1 - Travel Activity Dispute", "L1_T5_TAS_3": "Level 1 - ChatGPT Evaluation", "L1_T6_TAS_1": "Level 1 - Important Person", "L1_T6_TAS_2": "Level 1 - Memorable Trip", "L1_T6_TAS_3": "Level 1 - Famous Person Life", "L1_T7_TAS_1": "Level 1 - Tech Future", "L1_T7_TAS_2": "Level 1 - Life Next Year", "L1_T7_TAS_3": "Level 1 - Tech Future Extension", "L1_T8_TAS_1": "Level 1 - Well-Known Place", "L1_T8_TAS_2": "Level 1 - Tech Invention", "L1_T8_TAS_3": "Level 1 - Dish Preparation", "L1_T9_TAS_1": "Level 1 - Travel Guide", "L1_T9_TAS_2": "Level 1 - Sports Event Instructions", "L1_T9_TAS_3": "Level 1 - Healthy Lifestyle Advice", "L1_T10_TAS_1": "Level 1 - Travel Preferences", # Level 2 Tasks "L2_T1_TAS_1": "Level 2 - Social Media Pros/Cons", "L2_T2_TAS_1": "Level 2 - Professional Goals", "L2_T2_TAS_2": "Level 2 - Weather Advice", "L2_T2_TAS_3": "Level 2 - Conditional Sentences", "L2_T3_TAS_1": "Level 2 - Important Event", "L2_T3_TAS_2": "Level 2 - Achieved Goal", "L2_T3_TAS_3": "Level 2 - Tech Evolution", "L2_T4_TAS_1": "Level 2 - Event Organization", "L2_T4_TAS_2": "Level 2 - Learning Story", "L2_T4_TAS_3": "Level 2 - Team Problem", "L2_T5_TAS_1": "Level 2 - Morning Routine Benefits", "L2_T5_TAS_2": "Level 2 - Creative Activities", "L2_T5_TAS_3": "Level 2 - Pets' Happiness", "L2_T6_TAS_1": "Level 2 - Sports Milestone", "L2_T6_TAS_2": "Level 2 - Impactful Movie", "L2_T7_TAS_1": "Level 2 - Second Conditional", "L2_T7_TAS_2": "Level 2 - Conditional Movie Role", "L2_T7_TAS_3": "Level 2 - Conditional Scenarios", "L2_T8_TAS_1": "Level 2 - Artist Profile", "L2_T8_TAS_2": "Level 2 - Company Growth", "L2_T8_TAS_3": "Level 2 - Scientific Discovery", "L2_T9_TAS_1": "Level 2 - Movie Review", "L2_T9_TAS_2": "Level 2 - Seminar Report", "L2_T10_TAS_1": "Level 2 - AI Investment", # Level 3 Tasks "L3_T1_TAS_1": "Level 3 - Funding Priorities", "L3_T2_TAS_1": "Level 3 - Path Choice Reflection", "L3_T2_TAS_2": "Level 3 - Historical Outcome", "L3_T2_TAS_3": "Level 3 - Invention Impact", "L3_T3_TAS_1": "Level 3 - Business Pitch", "L3_T3_TAS_2": "Level 3 - Sports Lesson", "L3_T3_TAS_3": "Level 3 - Emotional Movie Review", "L3_T4_TAS_1": "Level 3 - Fitness Routine", "L3_T4_TAS_2": "Level 3 - Skill Improvement", "L3_T5_TAS_1": "Level 3 - Acting Preparation", "L3_T5_TAS_2": "Level 3 - Signature Dish", "L3_T5_TAS_3": "Level 3 - Explorer's Adventure", "L3_T6_TAS_1": "Level 3 - Memorable Character", "L3_T6_TAS_2": "Level 3 - Historical Figure", "L3_T6_TAS_3": "Level 3 - Influential Person", "L3_T7_TAS_1": "Level 3 - City Future", "L3_T7_TAS_2": "Level 3 - Future Transportation", "L3_T7_TAS_3": "Level 3 - Future Jobs", "L3_T8_TAS_1": "Level 3 - Missing Singer", "L3_T8_TAS_2": "Level 3 - Company Spy", "L3_T9_TAS_1": "Level 3 - Impact Event", "L3_T9_TAS_2": "Level 3 - Moment of Recognition", "L3_T10_TAS_1": "Level 3 - Environmental Campaign", } TASK_BACK_MAPPING = { "L1_T1_TAS_1": "user_dashboard", "L1_T2_TAS_1": "TT_L1_T1_3", "L1_T2_TAS_2": "TT_L1_T1_3", "L1_T3_TAS_1": "TT_L1_T2_1", "L1_T3_TAS_2": "TT_L1_T2_1", "L1_T3_TAS_3": "TT_L1_T2_1", "L1_T4_TAS_1": "TT_L1_T3_1", "L1_T4_TAS_2": "TT_L1_T3_1", "L1_T5_TAS_1": "TT_L1_T4_2", "L1_T5_TAS_2": "TT_L1_T4_2", "L1_T5_TAS_3": "TT_L1_T4_2", "L1_T6_TAS_1": "TT_L1_T5_2", "L1_T6_TAS_2": "TT_L1_T5_2", "L1_T6_TAS_3": "TT_L1_T5_2", "L1_T7_TAS_1": "TT_L1_T6_1", "L1_T7_TAS_2": "TT_L1_T6_1", "L1_T8_TAS_1": "TT_L1_T7_1", "L1_T8_TAS_2": "TT_L1_T7_1", "L1_T8_TAS_3": "TT_L1_T7_1", "L1_T9_TAS_1": "TT_L1_T8_1", "L1_T9_TAS_2": "TT_L1_T8_1", "L1_T9_TAS_3": "TT_L1_T8_1", "L1_T10_TAS_1": "task_1", "L2_T1_TAS_1": "user_dashboard", "L2_T2_TAS_1": "TT_L2_T1_3", "L2_T2_TAS_2": "TT_L2_T1_3", "L2_T2_TAS_3": "TT_L2_T1_3", "L2_T3_TAS_1": "TT_L2_T2_1", "L2_T3_TAS_2": "TT_L2_T2_1", "L2_T3_TAS_3": "TT_L2_T2_1", "L2_T4_TAS_1": "TT_L2_T3_2", "L2_T4_TAS_2": "TT_L2_T3_2", "L2_T4_TAS_3": "TT_L2_T3_2", "L2_T5_TAS_1": "TT_L2_T4_1", "L2_T5_TAS_2": "TT_L2_T4_1", "L2_T5_TAS_3": "TT_L2_T4_1", "L2_T6_TAS_1": "TT_L2_T5_1", "L2_T6_TAS_2": "TT_L2_T5_1", "L2_T7_TAS_1": "TT_L2_T6_1", "L2_T7_TAS_2": "TT_L2_T6_1", "L2_T7_TAS_3": "TT_L2_T6_1", "L2_T8_TAS_1": "TT_L2_T7_1", "L2_T8_TAS_2": "TT_L2_T7_1", "L2_T8_TAS_3": "TT_L2_T7_1", "L2_T9_TAS_1": "TT_L2_T8_2", "L2_T9_TAS_2": "TT_L2_T8_2", "L2_T10_TAS_1": "task_1", "L3_T1_TAS_1": "user_dashboard", "L3_T2_TAS_1": "TT_L3_T1_3", "L3_T2_TAS_2": "TT_L3_T1_3", "L3_T2_TAS_3": "TT_L3_T1_3", "L3_T3_TAS_1": "TT_L3_T2_1", "L3_T3_TAS_2": "TT_L3_T2_1", "L3_T3_TAS_3": "TT_L3_T2_1", "L3_T4_TAS_1": "TT_L3_T3_1", "L3_T4_TAS_2": "TT_L3_T3_1", "L3_T5_TAS_1": "TT_L3_T4_2", "L3_T5_TAS_2": "TT_L3_T4_2", "L3_T5_TAS_3": "TT_L3_T4_2", "L3_T6_TAS_1": "TT_L3_T5_1", "L3_T6_TAS_2": "TT_L3_T5_1", "L3_T6_TAS_3": "TT_L3_T5_1", "L3_T7_TAS_1": "TT_L3_T6_1", "L3_T7_TAS_2": "TT_L3_T6_1", "L3_T7_TAS_3": "TT_L3_T6_1", "L3_T8_TAS_1": "TT_L3_T7_1", "L3_T8_TAS_2": "TT_L3_T7_1", "L3_T9_TAS_1": "TT_L3_T8_1", "L3_T9_TAS_2": "TT_L3_T8_1", "L3_T10_TAS_1": "task_1" } # Define the mapping from task IDs to rule IDs TASK_RULE_MAPPING = { "L1_T1_TAS_1": "L1_T1_TAS_1", "L1_T2_TAS_1": "L1_T2_TAS_1", "L1_T2_TAS_2": "L1_T2_TAS_2", "L1_T3_TAS_1": "L1_T3_TAS_1", "L1_T3_TAS_2": "L1_T3_TAS_2", "L1_T3_TAS_3": "L1_T3_TAS_3", "L1_T4_TAS_1": "L1_T4_TAS_1", "L1_T4_TAS_2": "L1_T4_TAS_2", "L1_T5_TAS_1": "L1_T5_TAS_1", "L1_T5_TAS_2": "L1_T5_TAS_2", "L1_T5_TAS_3": "L1_T5_TAS_3", "L1_T6_TAS_1": "L1_T6_TAS_1", "L1_T6_TAS_2": "L1_T6_TAS_2", "L1_T6_TAS_3": "L1_T6_TAS_3", "L1_T7_TAS_1": "L1_T7_TAS_1", "L1_T7_TAS_2": "L1_T7_TAS_2", "L1_T8_TAS_1": "L1_T8_TAS_1", "L1_T8_TAS_2": "L1_T8_TAS_2", "L1_T8_TAS_3": "L1_T8_TAS_3", "L1_T9_TAS_1": "L1_T9_TAS_1", "L1_T9_TAS_2": "L1_T9_TAS_2", "L1_T9_TAS_3": "L1_T9_TAS_3", "L1_T10_TAS_1": "L1_T10_TAS_1", "L2_T1_TAS_1": "L2_T1_TAS_1", "L2_T2_TAS_1": "L2_T2_TAS_1", "L2_T2_TAS_2": "L2_T2_TAS_2", "L2_T2_TAS_3": "L2_T2_TAS_3", "L2_T3_TAS_1": "L2_T3_TAS_1", "L2_T3_TAS_2": "L2_T3_TAS_2", "L2_T3_TAS_3": "L2_T3_TAS_3", "L2_T4_TAS_1": "L2_T4_TAS_1", "L2_T4_TAS_2": "L2_T4_TAS_2", "L2_T4_TAS_3": "L2_T4_TAS_3", "L2_T5_TAS_1": "L2_T5_TAS_1", "L2_T5_TAS_2": "L2_T5_TAS_2", "L2_T5_TAS_3": "L2_T5_TAS_3", "L2_T6_TAS_1": "L2_T6_TAS_1", "L2_T6_TAS_2": "L2_T6_TAS_2", "L2_T7_TAS_1": "L2_T7_TAS_1", "L2_T7_TAS_2": "L2_T7_TAS_2", "L2_T7_TAS_3": "L2_T7_TAS_3", "L2_T8_TAS_1": "L2_T8_TAS_1", "L2_T8_TAS_2": "L2_T8_TAS_2", "L2_T8_TAS_3": "L2_T8_TAS_3", "L2_T9_TAS_1": "L2_T9_TAS_1", "L2_T9_TAS_2": "L2_T9_TAS_2", "L2_T10_TAS_1": "L2_T10_TAS_1", "L3_T1_TAS_1": "L3_T1_TAS_1", "L3_T2_TAS_1": "L3_T2_TAS_1", "L3_T2_TAS_2": "L3_T2_TAS_2", "L3_T2_TAS_3": "L3_T2_TAS_3", "L3_T3_TAS_1": "L3_T3_TAS_1", "L3_T3_TAS_2": "L3_T3_TAS_2", "L3_T3_TAS_3": "L3_T3_TAS_3", "L3_T4_TAS_1": "L3_T4_TAS_1", "L3_T4_TAS_2": "L3_T4_TAS_2", "L3_T5_TAS_1": "L3_T5_TAS_1", "L3_T5_TAS_2": "L3_T5_TAS_2", "L3_T5_TAS_3": "L3_T5_TAS_3", "L3_T6_TAS_1": "L3_T6_TAS_1", "L3_T6_TAS_2": "L3_T6_TAS_2", "L3_T6_TAS_3": "L3_T6_TAS_3", "L3_T7_TAS_1": "L3_T7_TAS_1", "L3_T7_TAS_2": "L3_T7_TAS_2", "L3_T7_TAS_3": "L3_T7_TAS_3", "L3_T8_TAS_1": "L3_T8_TAS_1", "L3_T8_TAS_2": "L3_T8_TAS_2", "L3_T9_TAS_1": "L3_T9_TAS_1", "L3_T9_TAS_2": "L3_T9_TAS_2", "L3_T10_TAS_1": "L3_T10_TAS_1" } def L1_T10_TAS_1(): navigate_to_task1_9() st.rerun() def L2_T10_TAS_1(): navigate_to_task2_9() st.rerun() def L3_T10_TAS_1(): navigate_to_task3_9() st.rerun() # Define a mapping from each task to its next page task_to_next_page = { "L1_T1_TAS_1": "TT_L1_T1_1", "L1_T2_TAS_1": "TT_L1_T2_1", "L1_T2_TAS_2": "TT_L1_T2_1", "L1_T3_TAS_1": "TT_L1_T3_1", "L1_T3_TAS_2": "TT_L1_T3_1", "L1_T3_TAS_3": "TT_L1_T3_1", "L1_T4_TAS_1": "TT_L1_T4_1", "L1_T4_TAS_2": "TT_L1_T4_1", "L1_T5_TAS_1": "TT_L1_T5_1", "L1_T5_TAS_2": "TT_L1_T5_1", "L1_T5_TAS_3": "TT_L1_T5_1", "L1_T6_TAS_1": "TT_L1_T6_1", "L1_T6_TAS_2": "TT_L1_T6_1", "L1_T6_TAS_3": "TT_L1_T6_1", "L1_T7_TAS_1": "TT_L1_T7_1", "L1_T7_TAS_2": "TT_L1_T7_1", "L1_T8_TAS_1": "TT_L1_T8_1", "L1_T8_TAS_2": "TT_L1_T8_1", "L1_T8_TAS_3": "TT_L1_T8_1", "L1_T9_TAS_1": "L1_T10_TAS_1", "L1_T9_TAS_2": "L1_T10_TAS_1", "L1_T9_TAS_3": "L1_T10_TAS_1", "L1_T10_TAS_1": "success_page", "L2_T1_TAS_1": "TT_L2_T1_1", "L2_T2_TAS_1": "TT_L2_T2_1", "L2_T2_TAS_2": "TT_L2_T2_1", "L2_T2_TAS_3": "TT_L2_T2_1", "L2_T3_TAS_1": "TT_L2_T3_1", "L2_T3_TAS_2": "TT_L2_T3_1", "L2_T3_TAS_3": "TT_L2_T3_1", "L2_T4_TAS_1": "TT_L2_T4_1", "L2_T4_TAS_2": "TT_L2_T4_1", "L2_T4_TAS_3": "TT_L2_T4_1", "L2_T5_TAS_1": "TT_L2_T5_1", "L2_T5_TAS_2": "TT_L2_T5_1", "L2_T5_TAS_3": "TT_L2_T5_1", "L2_T6_TAS_1": "TT_L2_T6_1", "L2_T6_TAS_2": "TT_L2_T6_1", "L2_T7_TAS_1": "TT_L2_T7_1", "L2_T7_TAS_2": "TT_L2_T7_1", "L2_T7_TAS_3": "TT_L2_T7_1", "L2_T8_TAS_1": "TT_L2_T8_1", "L2_T8_TAS_2": "TT_L2_T8_1", "L2_T8_TAS_3": "L2_T10_TAS_1", "L2_T9_TAS_1": "L2_T10_TAS_1", "L2_T9_TAS_2": "L2_T10_TAS_1", "L2_T10_TAS_1": "success_page", "L3_T1_TAS_1": "TT_L3_T1_1", "L3_T2_TAS_1": "TT_L3_T2_1", "L3_T2_TAS_2": "TT_L3_T2_1", "L3_T2_TAS_3": "TT_L3_T2_1", "L3_T3_TAS_1": "TT_L3_T3_1", "L3_T3_TAS_2": "TT_L3_T3_1", "L3_T3_TAS_3": "TT_L3_T3_1", "L3_T4_TAS_1": "TT_L3_T4_1", "L3_T4_TAS_2": "TT_L3_T4_1", "L3_T5_TAS_1": "TT_L3_T5_1", "L3_T5_TAS_2": "TT_L3_T5_1", "L3_T5_TAS_3": "TT_L3_T5_1", "L3_T6_TAS_1": "TT_L3_T6_1", "L3_T6_TAS_2": "TT_L3_T6_1", "L3_T6_TAS_3": "TT_L3_T6_1", "L3_T7_TAS_1": "TT_L3_T7_1", "L3_T7_TAS_2": "TT_L3_T7_1", "L3_T7_TAS_3": "TT_L3_T7_1", "L3_T8_TAS_1": "TT_L3_T8_1", "L3_T8_TAS_2": "TT_L3_T8_1", "L3_T9_TAS_1": "L3_T10_TAS_1", "L3_T9_TAS_2": "L3_T10_TAS_1", "L3_T10_TAS_1": "success_page" } def navigate_to_page(page_key): """Navigate to the specified page.""" st.session_state.page = page_key tasks_dict = load_tasks() rules = load_rules() def get_rule_content(rule_id): """ Retrieves the rule content from the rules dictionary based on the rule_id. """ if rule_id in rules: rule_content = rules[rule_id].get('rule') return rule_content def navigate_back(): """ Sets st.session_state.page to whatever was stored as 'previous_page'. If none is found, defaults to some other page, e.g. 'main_page'. """ st.session_state.page = st.session_state.get('previous_page') def practice_page(): """ Displays a practice page with no dropdown, auto-loading questions for the tutorial in st.session_state.selected_tutorial. Also adds a 'Back' button to return to the page from which you arrived. """ st.title("Grammar Practice") st.write("Below are the questions for your selected tutorial:") # --- 1) 'Back' button at the top (or you can place it at the bottom) --- # --- 2) Load all tasks if needed --- if "all_tasks" not in st.session_state: script_dir = os.path.dirname(os.path.abspath(__file__)) file_path = os.path.join(script_dir, "data/practice.json") with open(file_path, "r") as f: st.session_state.all_tasks = json.load(f) # --- 3) Determine which tutorial is selected --- selected_tutorial = st.session_state.get("selected_tutorial", None) if not selected_tutorial: st.info("No tutorial selected.") return # --- 4) Filter tasks for this tutorial --- filtered_tasks = [ t for t in st.session_state.all_tasks if selected_tutorial in t["Tutorial number"] ] if not filtered_tasks: st.warning(f"No tasks found for tutorial '{selected_tutorial}'.") return # --- 5) Check if we have a stored set of questions for this tutorial --- if "selected_questions" not in st.session_state: st.session_state.selected_questions = {} if selected_tutorial not in st.session_state.selected_questions: # pick up to 8 questions at random chosen_ques = random.sample(filtered_tasks, min(8, len(filtered_tasks))) st.session_state.selected_questions[selected_tutorial] = chosen_ques questions_for_tutorial = st.session_state.selected_questions[selected_tutorial] # --- 6) Store user answers keyed by tutorial --- if "user_answers" not in st.session_state: st.session_state.user_answers = {} if selected_tutorial not in st.session_state.user_answers: # Initialize answers st.session_state.user_answers[selected_tutorial] = [None] * len(questions_for_tutorial) user_answers = st.session_state.user_answers[selected_tutorial] # --- 7) Display each question & a radio for answers --- for i, q in enumerate(questions_for_tutorial): st.write(f"**Question {i + 1}:** {q['Task']}") options = q["Options"] # Figure out which option the user previously chose current_answer = user_answers[i] default_index = 0 if current_answer in options: default_index = options.index(current_answer) user_answers[i] = st.radio( label="", options=options, index=None, key=f"{selected_tutorial}_q_{i}" ) st.write("---") # --- 8) Check Answers button --- if st.button("Check Answers"): correct_count = 0 for i, q in enumerate(questions_for_tutorial): correct_ans = q["Correct option"] if user_answers[i] == correct_ans: correct_count += 1 st.write(f"**Score**: {correct_count}/{len(questions_for_tutorial)}") if st.button("Back"): navigate_back() st.rerun() # stop here so we don't keep drawing this page def success_page(): st.title("Congratulations!") st.write("You have successfully completed all tasks.") # If these session keys are used elsewhere, keep them: if 'selected_tutorial' not in st.session_state: st.session_state.selected_tutorial = None if 'practice_questions' not in st.session_state: st.session_state.practice_questions = [] import streamlit as st import streamlit.components.v1 as components from sqlalchemy.exc import SQLAlchemyError import random import json def spelling_practice_page(): st.title("Spelling Practice Page") # Define styles styles = """ """ st.markdown(styles, unsafe_allow_html=True) # Function to sanitize words (remove non-alphabetic characters) def sanitize_word(word): return ''.join(filter(str.isalpha, word)) # Function to scramble a word def scramble_word(word): word = sanitize_word(word.lower()) if len(word) <= 1: return word # No scramble needed for single-letter words letters = list(word) scrambled = word attempts = 0 # Attempt to scramble until it's different from the original while scrambled == word and attempts < 10: random.shuffle(letters) scrambled = ''.join(letters) attempts += 1 return scrambled # Retrieve logged-in student data from session state student = st.session_state.get('user_info') # Ensure 'user_info' contains the student object if not student: st.error("No user is currently logged in.") return # Fetch Errors for the Logged-in Student try: errors_obj = session.query(Errors).filter_by(student_id=student.id).one_or_none() except SQLAlchemyError as e: st.error(f"Error fetching errors: {e}") return if errors_obj and errors_obj.spell_err: # Convert all words to lowercase and sanitize spell_words = [sanitize_word(word.lower()) for word in errors_obj.spell_err] # Remove any empty strings resulted from sanitization spell_words = [word for word in spell_words if word] else: spell_words = [] if not spell_words: st.info("No spelling errors to practice.") return # If more than 10 words, randomly select 10 max_tasks = 10 if len(spell_words) > max_tasks: spell_words = random.sample(spell_words, max_tasks) # Function to generate scramble task HTML and JavaScript def generate_scramble_task(word, index): scrambled = scramble_word(word) container_id = f"scramble_container_{index}" drag_container_id = f"drag-container-{index}" drop_container_id = f"drop-container-{index}" result_id = f"result_{index}" check_button_id = f"check-btn-{index}" reset_button_id = f"reset-btn-{index}" correct_answer_js = json.dumps(word) # Ensure proper JSON formatting # Generate draggable divs draggable_html = ''.join([f'
    {letter}
    ' for letter in scrambled]) # HTML and JavaScript for the scramble task task_html = f"""

    Arrange the letters to form the correct word:

    {draggable_html}

    """ return task_html # Loop through each word and generate scramble tasks for idx, word in enumerate(spell_words, start=1): scramble_task_html = generate_scramble_task(word, idx) # Render the scramble task using Streamlit's components.html components.html(scramble_task_html, height=300, scrolling=True) def navigate_to_task1_0(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T2_TAS_1' def navigate_to_task1_1(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T2_TAS_1' def navigate_to_task1_2(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T3_TAS_1' def navigate_to_task1_3(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T4_TAS_1' def navigate_to_task1_4(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T5_TAS_1' def navigate_to_task1_5(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T6_TAS_1' def navigate_to_task1_6(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T7_TAS_1' def navigate_to_task1_7(): """ Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T8_TAS_1' def navigate_to_task1_8(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T9_TAS_1' def navigate_to_task1_9(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L1_T10_TAS_1' def navigate_to_task2_0(): """ Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T1_TAS_1' def navigate_to_task2_1(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T2_TAS_1' def navigate_to_task2_2(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T3_TAS_1' def navigate_to_task2_3(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T4_TAS_1' def navigate_to_task2_4(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T5_TAS_1' def navigate_to_task2_5(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T6_TAS_1' def navigate_to_task2_6(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T7_TAS_1' def navigate_to_task2_7(): """ Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T8_TAS_1' def navigate_to_task2_8(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T9_TAS_1' def navigate_to_task2_9(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L2_T10_TAS_1' def navigate_to_task3_0(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T1_TAS_1' def navigate_to_task3_1(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T2_TAS_1' def navigate_to_task3_2(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T3_TAS_1' def navigate_to_task3_3(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T4_TAS_1' def navigate_to_task3_4(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T5_TAS_1' def navigate_to_task3_5(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T6_TAS_1' def navigate_to_task3_6(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T7_TAS_1' def navigate_to_task3_7(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T8_TAS_1' def navigate_to_task3_8(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T9_TAS_1' def navigate_to_task3_9(): """Navigate to Task 1 page and set the current task.""" st.session_state.page = 'task_1' st.session_state.task_selection = 'L3_T10_TAS_1' def navigate_to_tt_l1_t1_1(): st.session_state.page = 'TT_L1_T1_1' def navigate_to_tt_l1_t1_2(): st.session_state.page = 'TT_L1_T1_2' st.session_state.tt_l1_t1_1_all_correct = False def navigate_to_tt_l1_t1_3(): st.session_state.page = 'TT_L1_T1_3' st.session_state.tt_l1_t1_2_all_correct = False def navigate_to_tt_l1_t2_1(): st.session_state.page = 'TT_L1_T2_1' def navigate_to_tt_l1_t3_1(): st.session_state.page = 'TT_L1_T3_1' def navigate_to_tt_l1_t4_1(): st.session_state.page = 'TT_L1_T4_1' def navigate_to_tt_l1_t4_2(): st.session_state.page = 'TT_L1_T4_2' st.session_state.tt_l1_t4_1_all_correct = False def navigate_to_tt_l1_t5_1(): st.session_state.page = 'TT_L1_T5_1' def navigate_to_tt_l1_t5_2(): st.session_state.page = 'TT_L1_T5_2' st.session_state.tt_l1_t5_1_all_correct = False def navigate_to_tt_l1_t6_1(): st.session_state.page = 'TT_L1_T6_1' def navigate_to_tt_l1_t7_1(): st.session_state.page = 'TT_L1_T7_1' def navigate_to_tt_l1_t8_1(): st.session_state.page = 'TT_L1_T8_1' def navigate_to_tt_l2_t1_1(): st.session_state.page = 'TT_L2_T1_1' def navigate_to_tt_l2_t1_2(): st.session_state.page = 'TT_L2_T1_2' st.session_state.tt_l2_t1_1_all_correct = False def navigate_to_tt_l2_t1_3(): st.session_state.page = 'TT_L2_T1_3' st.session_state.tt_l2_t1_2_all_correct = False def navigate_to_tt_l2_t2_1(): st.session_state.page = 'TT_L2_T2_1' def navigate_to_tt_l2_t3_1(): st.session_state.page = 'TT_L2_T3_1' def navigate_to_tt_l2_t3_2(): st.session_state.page = 'TT_L2_T3_2' st.session_state.tt_l2_t3_1_all_correct = False def navigate_to_tt_l2_t4_1(): st.session_state.page = 'TT_L2_T4_1' def navigate_to_tt_l2_t5_1(): st.session_state.page = 'TT_L2_T5_1' def navigate_to_tt_l2_t6_1(): st.session_state.page = 'TT_L2_T6_1' def navigate_to_tt_l2_t7_1(): st.session_state.page = 'TT_L2_T7_1' def navigate_to_tt_l2_t8_1(): st.session_state.page = 'TT_L2_T8_1' def navigate_to_tt_l2_t8_2(): st.session_state.page = 'TT_L2_T8_2' st.session_state.tt_l2_t8_1_all_correct = False def navigate_to_tt_l3_t1_1(): st.session_state.page = 'TT_L3_T1_1' def navigate_to_tt_l3_t1_2(): st.session_state.page = 'TT_L3_T1_2' st.session_state.tt_l3_t1_1_all_correct = False def navigate_to_tt_l3_t1_3(): st.session_state.page = 'TT_L3_T1_3' st.session_state.tt_l3_t1_2_all_correct = False def navigate_to_tt_l3_t2_1(): st.session_state.page = 'TT_L3_T2_1' def navigate_to_tt_l3_t3_1(): st.session_state.page = 'TT_L3_T3_1' def navigate_to_tt_l3_t4_1(): st.session_state.page = 'TT_L3_T4_1' def navigate_to_tt_l3_t4_2(): st.session_state.page = 'TT_L3_T4_2' st.session_state.tt_l3_t4_1_all_correct = False def navigate_to_tt_l3_t5_1(): st.session_state.page = 'TT_L3_T5_1' def navigate_to_tt_l3_t6_1(): st.session_state.page = 'TT_L3_T6_1' def navigate_to_tt_l3_t7_1(): st.session_state.page = 'TT_L3_T7_1' def navigate_to_tt_l3_t8_1(): st.session_state.page = 'TT_L3_T8_1' def back_to_tt_l1_t1_1(): st.session_state.page = 'TT_L1_T1_1' st.session_state.tt_l1_t1_1_all_correct = False def back_to_tt_l1_t1_2(): st.session_state.page = 'TT_L1_T1_2' st.session_state.tt_l1_t1_2_all_correct = False def back_to_tt_l1_t4_1(): st.session_state.page = 'TT_L1_T4_1' st.session_state.tt_l1_t4_1_all_correct = False def back_to_tt_l1_t5_1(): st.session_state.page = 'TT_L1_T5_1' st.session_state.tt_l1_t5_1_all_correct = False def back_to_tt_l2_t1_1(): st.session_state.page = 'TT_L2_T1_1' def back_to_tt_l2_t1_2(): st.session_state.page = 'TT_L2_T1_2' st.session_state.tt_l2_t1_1_all_correct = False def back_to_tt_l2_t3_1(): st.session_state.page = 'TT_L2_T3_1' def back_to_tt_l2_t8_1(): st.session_state.page = 'TT_L2_T8_1' def back_to_tt_l3_t1_1(): st.session_state.page = 'TT_L3_T1_1' def back_to_tt_l3_t1_2(): st.session_state.page = 'TT_L3_T1_2' st.session_state.tt_l3_t1_1_all_correct = False def back_to_tt_l3_t4_1(): st.session_state.page = 'TT_L3_T4_1' def set_selectbox_style(element_index, color, padding): js_code = f""" """ components.html(js_code, height=0, width=0) def navigate_to_practice(tutorial_number: str): st.session_state.page = 'practice_page' st.session_state.task_selection = tutorial_number def navigate_to_practice_TT_L1_T1_3(): st.session_state["previous_page"] = "TT_L1_T1_3" st.session_state.selected_tutorial = 'TT_L1_T1_3' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T1_2(): st.session_state["previous_page"] = "TT_L1_T1_2" st.session_state.selected_tutorial = 'TT_L1_T1_2' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T2_1(): st.session_state["previous_page"] = "TT_L1_T2_1" st.session_state.page = 'practice_page' st.session_state.selected_tutorial = 'TT_L1_T2_1' def navigate_to_practice_TT_L1_T3_1(): st.session_state["previous_page"] = "TT_L1_T3_1" st.session_state.selected_tutorial = 'TT_L1_T3_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T4_1(): st.session_state["previous_page"] = "TT_L1_T4_1" st.session_state.selected_tutorial = 'TT_L1_T4_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T4_2(): st.session_state["previous_page"] = "TT_L1_T4_2" st.session_state.selected_tutorial = 'TT_L1_T4_2' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T5_1(): st.session_state["previous_page"] = "TT_L1_T5_1" st.session_state.selected_tutorial = 'TT_L1_T5_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T5_2(): st.session_state["previous_page"] = "TT_L1_T5_2" st.session_state.selected_tutorial = 'TT_L1_T5_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T6_1(): st.session_state["previous_page"] = "TT_L1_T6_1" st.session_state.selected_tutorial = 'TT_L1_T6_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T7_1(): st.session_state["previous_page"] = "TT_L1_T7_1" st.session_state.selected_tutorial = 'TT_L1_T7_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L1_T8_1(): st.session_state["previous_page"] = "TT_L1_T8_1" st.session_state.selected_tutorial = 'TT_L1_T8_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T1_2(): st.session_state["previous_page"] = "TT_L2_T1_2" st.session_state.selected_tutorial = 'TT_L2_T1_2' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T1_3(): st.session_state["previous_page"] = "TT_L2_T1_3" st.session_state.selected_tutorial = 'TT_L2_T1_3' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T2_1(): st.session_state["previous_page"] = "TT_L2_T2_1" st.session_state.selected_tutorial = 'TT_L2_T2_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T3_1(): st.session_state["previous_page"] = "TT_L2_T3_1" st.session_state.selected_tutorial = 'TT_L2_T3_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T3_2(): st.session_state["previous_page"] = "TT_L2_T3_2" st.session_state.selected_tutorial = 'TT_L2_T3_2' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T4_1(): st.session_state["previous_page"] = "TT_L2_T4_1" st.session_state.selected_tutorial = 'TT_L2_T4_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T5_1(): st.session_state["previous_page"] = "TT_L2_T5_1" st.session_state.selected_tutorial = 'TT_L2_T5_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T6_1(): st.session_state["previous_page"] = "TT_L2_T6_1" st.session_state.selected_tutorial = 'TT_L2_T6_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T7_1(): st.session_state["previous_page"] = "TT_L2_T7_1" st.session_state.selected_tutorial = 'TT_L2_T7_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T8_1(): st.session_state["previous_page"] = "TT_L2_T8_1" st.session_state.selected_tutorial = 'TT_L2_T8_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L2_T8_2(): st.session_state["previous_page"] = "TT_L2_T8_2" st.session_state.selected_tutorial = 'TT_L2_T8_2' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T1_2(): st.session_state["previous_page"] = "TT_L3_T1_2" st.session_state.selected_tutorial = 'TT_L3_T1_2' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T1_3(): st.session_state["previous_page"] = "TT_L3_T1_3" st.session_state.selected_tutorial = 'TT_L3_T1_3' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T2_1(): st.session_state["previous_page"] = "TT_L3_T2_1" st.session_state.selected_tutorial = 'TT_L3_T2_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T3_1(): st.session_state["previous_page"] = "TT_L3_T3_1" st.session_state.selected_tutorial = 'TT_L3_T3_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T4_1(): st.session_state["previous_page"] = "TT_L3_T4_1" st.session_state.selected_tutorial = 'TT_L3_T4_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T4_2(): st.session_state["previous_page"] = "TT_L3_T4_2" st.session_state.selected_tutorial = 'TT_L3_T4_2' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T5_1(): st.session_state["previous_page"] = "TT_L3_T5_1" st.session_state.selected_tutorial = 'TT_L3_T5_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T6_1(): st.session_state["previous_page"] = "TT_L3_T6_1" st.session_state.selected_tutorial = 'TT_L3_T6_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T7_1(): st.session_state["previous_page"] = "TT_L3_T7_1" st.session_state.selected_tutorial = 'TT_L3_T7_1' st.session_state.page = 'practice_page' def navigate_to_practice_TT_L3_T8_1(): st.session_state["previous_page"] = "TT_L3_T8_1" st.session_state.selected_tutorial = 'TT_L3_T8_1' st.session_state.page = 'practice_page' def TT_L1_T1_1(): st.title("TT_L1_T1_1: Introduction") # 1. Initialize session state for timing if "tt_l1_t1_1_start_time" not in st.session_state: st.session_state.tt_l1_t1_1_start_time = datetime.datetime.now() add_css() # Existing content for TT_L1_T1_1() st.markdown("""

    πŸ“š Understanding Parts of Speech

    Use the dropdown lists below to explore different parts of speech.

    """, unsafe_allow_html=True) # Part of Speech Selection col1, col2, col3, col4, col5 = st.columns(5) with col1: selected_determiner = st.selectbox('Determiner', ['a', 'the', 'some', 'any'], key='selectbox1') with col2: selected_noun = st.selectbox('Noun', ['car', 'dog', 'house', 'book'], key='selectbox2') with col3: selected_verb = st.selectbox('Verb', ['run', 'jump', 'swim', 'read'], key='selectbox3') with col4: selected_adjective = st.selectbox('Adjective', ['red', 'big', 'quick', 'blue'], key='selectbox4') with col5: selected_adverb = st.selectbox('Adverb', ['quickly', 'silently', 'well', 'badly'], key='selectbox5') # Understanding Phrases Section with updated yellow color st.markdown("""

    πŸ“š Understanding Noun Phrases and Verb Phrases

    A noun phrase (yellow) is a group of words that functions as a noun in a sentence. It typically includes a noun and its modifiers.

    A verb phrase (red) consists of a verb and any accompanying words, such as auxiliaries, complements, or modifiers.

    Noun Phrases

    a car
    the red house

    Verb Phrases

    run
    is running
    run quickly
    """, unsafe_allow_html=True) # Examples Section inside Green Frame with updated yellow color st.markdown("""

    πŸ“ Examples

    The manager
    is reviewing
    the report.
    The students
    are studying
    for the exam.
    """, unsafe_allow_html=True) # Practice Section with Drag and Drop (Kept as is) st.markdown("""

    🎯 Practice

    Practice combining noun and verb phrases by dragging and dropping them into the correct order to form sentences related to work and studies.

    """, unsafe_allow_html=True) # Drag-and-Drop Interface drag_and_drop_css = """ """ # Updated Drag-and-Drop JS without the "Next" button and with page reload on correct answers drag_and_drop_js = """

    Arrange the phrases to form the correct sentences

    the project.
    The manager
    is giving
    The professor
    is working on
    the report.
    a lecture.
    is reviewing
    The team

    """ # Combine CSS and JS combined_html = drag_and_drop_css + drag_and_drop_js # Embed the HTML (drag-and-drop interface) components.html(combined_html, height=500, scrolling=True) # 2. "Next" button to record data and navigate if st.button("Next"): # a) Compute total time end_time = datetime.datetime.now() start_time = st.session_state.tt_l1_t1_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" # Convert to a time object try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback # b) Prepare errors dictionary (no correctness check here) errors_info = {"error_count": 0} # c) Insert into tasks_done try: student_id = st.session_state.user_info.id task_id = "TT_L1_T1_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return # d) Reset session state or do any cleanup st.session_state.tt_l1_t1_1_start_time = None # e) Navigate to next page navigate_to_tt_l1_t1_2() st.rerun() def record_task_done(student_id, task_id, date, time_spent, errors_dict=None): """ Insert a record into the tasks_done table. :param student_id: ID of the student completing the task. :param task_id: ID of the task completed. :param date: Datetime when the task was completed. :param time_spent: Time object representing duration spent on the task. :param errors_dict: Dictionary containing error information. """ task_done_entry = TasksDone( student_id=student_id, task_id=task_id, date=date, time=time_spent, errors=errors_dict or {} ) session.add(task_done_entry) session.commit() st.success(f"Task {task_id} recorded successfully.") def complete_task_and_record_data(page_prefix, task_id, next_page_func): # 1) Calculate how long user spent on this page end_time = datetime.datetime.now() start_key = f"{page_prefix}_start_time" error_key = f"{page_prefix}_error_count" if start_key not in st.session_state: st.warning("No start time found in session state!") return start_time = st.session_state[start_key] # Time difference time_diff = end_time - start_time total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" # Convert to time object try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError: time_obj = None # 2) Prepare errors error_count = st.session_state.get(error_key, 0) errors_info = {"error_count": error_count} # 3) Insert into tasks_done try: student_id = st.session_state.user_info.id record_task_done( student_id=student_id, task_id=task_id, date=end_time, # pass the date here time_spent=time_obj, errors_dict=errors_info ) st.success(f"Task {task_id} recorded successfully.") except Exception as e: st.error(f"Error recording task: {e}") return # 4) Reset states if needed st.session_state[start_key] = None st.session_state[error_key] = 0 # 5) Finally, navigate next_page_func() def TT_L1_T1_2(): """ This page teaches Present Simple vs. Present Continuous. Records how long the student spends here and how many errors they make before getting all correct answers. """ # 1) Initialize session state if "tt_l1_t1_2_start_time" not in st.session_state: st.session_state.tt_l1_t1_2_start_time = datetime.datetime.now() if "tt_l1_t1_2_error_count" not in st.session_state: st.session_state.tt_l1_t1_2_error_count = 0 if "tt_l1_t1_2_all_correct" not in st.session_state: st.session_state.tt_l1_t1_2_all_correct = False # 2) Page Content st.title("TT_L1_T1_2: Present Simple vs Present Continuous") add_css() # Custom CSS if you have it # ---- Rules Section ---- st.markdown("""

    πŸ“’ Rules

    Present Simple: The Present Simple tense is used for habitual actions, general truths, or facts. It is formed using the base form of the verb for all subjects except the third-person singular (he, she, it), which adds an "s" or "es" to the verb.

    Key Words: always, usually, never, every day

    Example: The manager reviews reports every day.

    Present Continuous: The Present Continuous tense is used for actions happening right now or for temporary situations. It is formed using the present form of "to be" (am/is/are) + the "-ing" form of the verb.

    Key Words: now, at the moment, currently, right now

    Example: The manager is reviewing a report right now.

    """, unsafe_allow_html=True) # ---- Examples Section ---- st.markdown("""

    πŸ“ Examples

    Example 1: Present Simple

    The employee works on projects every day.

    Example 2: Present Continuous

    The employee is working on a new project right now.
    """, unsafe_allow_html=True) # ---- Practice Section ---- st.markdown("""

    🎯 Practice

    Complete the following sentences by selecting the correct verb form. Be careful with the tenses!

    """, unsafe_allow_html=True) # 3) Define practice items sentences = [ { 'sentence': "She _____ (work) on the new project every day.", 'options': ['works', 'is working', 'work', 'are working'], 'correct': 'works', 'tense': 'Present Simple' }, { 'sentence': "They _____ (study) for their exams right now.", 'options': ['study', 'are studying', 'studies', 'is studying'], 'correct': 'are studying', 'tense': 'Present Continuous' }, { 'sentence': "He _____ (attend) meetings every Monday.", 'options': ['attend', 'attends', 'is attending', 'are attending'], 'correct': 'attends', 'tense': 'Present Simple' }, { 'sentence': "We _____ (prepare) for the presentation at the moment.", 'options': ['prepare', 'prepares', 'are preparing', 'is preparing'], 'correct': 'are preparing', 'tense': 'Present Continuous' }, { 'sentence': "The company _____ (offer) great benefits.", 'options': ['offers', 'is offering', 'offer', 'are offering'], 'correct': 'offers', 'tense': 'Present Simple' } ] # 4) Collect user answers via radio buttons user_answers = [] for idx, item in enumerate(sentences): st.markdown(f"**{item['sentence']}**") user_choice = st.radio( "", options=item['options'], index=None, # No default selection key=f"tt_l1_t1_2_sentence_{idx}" ) user_answers.append(user_choice) # 7. Handle "Check" Button if st.button("Check"): score = sum( 1 for i, ans in enumerate(user_answers) if ans == sentences[i]["correct"] ) st.markdown(f"### Your Score: {score}/{len(sentences)}") if score == len(sentences): st.success("Great job!") st.session_state.tt_l1_t1_2_all_correct = True else: st.error("You made some mistakes.") st.session_state.tt_l1_t1_2_error_count += 1 # Handle "Next" if all correct if st.session_state.tt_l1_t1_2_all_correct: if st.button("Next", key='tt_l1_t1_2_next'): complete_task_and_record_data( page_prefix="tt_l1_t1_2", task_id="TT_L1_T1_2", next_page_func=navigate_to_tt_l1_t1_3 ) # "Back" button if st.button("Back"): back_to_tt_l1_t1_1() st.rerun() def TT_L1_T1_3(): st.title("TT_L1_T1_3: Present Simple vs Present Continuous") tutorial_number = "TT_L1_T1_3" # 1) Load questions (if needed) questions = load_questions([tutorial_number]) if not questions: st.error("No questions to display for this tutorial.") return add_css() # 2) Initialize session states # present_simple_correct and present_continuous_correct track # if user has answered each section correctly if "tt_l1_t1_3_start_time" not in st.session_state: st.session_state.tt_l1_t1_3_start_time = datetime.datetime.now() if "tt_l1_t1_3_error_count" not in st.session_state: st.session_state.tt_l1_t1_3_error_count = 0 if "present_simple_correct" not in st.session_state: st.session_state.present_simple_correct = False if "present_continuous_correct" not in st.session_state: st.session_state.present_continuous_correct = False # 3) Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Present Simple: Used for habitual actions or general truths.

    Keywords: usually, always, never, every day, etc.

    Example: He usually checks emails in the morning.

    Present Continuous: Used for actions happening right now or temporary situations.

    Keywords: now, right now, at the moment, currently.

    Example: He is checking emails right now.

    """, unsafe_allow_html=True) # 4) Examples Section st.markdown("""

    πŸ“ Examples

    Example 1: Present Simple

    The manager usually reviews reports.

    Example 2: Present Continuous

    She always attends meetings on Mondays.
    """, unsafe_allow_html=True) # 5) Practice Present Simple st.markdown("""

    🎯 Practice: Present Simple

    Select the correct verb form and adverb to complete the sentences.

    """, unsafe_allow_html=True) subjects = ['The manager', 'She', 'He', 'They', 'We'] verbs_present_simple = ['reviews', 'attends', 'works', 'studies', 'review', 'attend'] adverbs_simple = ['usually', 'always', 'never', 'every day', 'currently', 'now'] col1_simple, col2_simple, col3_simple = st.columns(3) with col1_simple: subject_simple = st.selectbox( 'Subject (Present Simple)', subjects, key='subject_simple' ) with col2_simple: adverb_simple = st.selectbox( 'Adverb (Present Simple)', adverbs_simple, key='adverb_simple' ) with col3_simple: verb_simple = st.selectbox( 'Verb (Present Simple)', verbs_present_simple, key='verb_simple' ) simple_sentence = f"{subject_simple} {adverb_simple} {verb_simple}." st.markdown(f""" ### Your Sentence: Present Simple
    {simple_sentence}
    """, unsafe_allow_html=True) # Button to check Present Simple correctness if st.button('Check Present Simple Sentence'): correct_simple = False if subject_simple in ['He', 'She', 'The manager']: if verb_simple.endswith('s') and adverb_simple in ['usually', 'always', 'never', 'every day']: correct_simple = True elif subject_simple in ['They', 'We']: if not verb_simple.endswith('s') and adverb_simple in ['usually', 'always', 'never', 'every day']: correct_simple = True if correct_simple: st.success("Present Simple: Correct!") st.session_state.present_simple_correct = True else: st.error("Present Simple: Incorrect. Check subject-verb agreement and tense usage.") st.session_state.present_simple_correct = False st.session_state.tt_l1_t1_3_error_count += 1 # 6) Practice Present Continuous st.markdown("""

    πŸ“ Examples: Present Continuous

    Expand to see examples

    Here are some examples of sentences using Present Continuous:

    The team is working on the project right now.
    He is studying for the exam at the moment.
    """, unsafe_allow_html=True) st.markdown("""

    🎯 Practice: Present Continuous

    Select the correct verb form and adverb to complete the sentences.

    """, unsafe_allow_html=True) verbs_present_continuous = [ 'is working', 'are working', 'is studying', 'are studying', 'working', 'studying' ] adverbs_continuous = [ 'now', 'right now', 'at the moment', 'currently', 'usually', 'every day' ] col1_cont, col2_cont, col3_cont = st.columns(3) with col1_cont: subject_continuous = st.selectbox( 'Subject (Present Continuous)', subjects, key='subject_continuous' ) with col2_cont: verb_continuous = st.selectbox( 'Verb (Present Continuous)', verbs_present_continuous, key='verb_continuous' ) with col3_cont: adverb_continuous = st.selectbox( 'Adverb (Present Continuous)', adverbs_continuous, key='adverb_continuous' ) continuous_sentence = f"{subject_continuous} {verb_continuous} {adverb_continuous}." st.markdown(f""" ### Your Sentence: Present Continuous
    {continuous_sentence}
    """, unsafe_allow_html=True) # Button to check Present Continuous correctness if st.button('Check Present Continuous Sentence'): correct_cont = False if subject_continuous in ['He', 'She', 'The manager']: if verb_continuous.startswith('is ') and verb_continuous.endswith('ing') \ and adverb_continuous in ['now', 'right now', 'at the moment', 'currently']: correct_cont = True elif subject_continuous in ['They', 'We']: if verb_continuous.startswith('are ') and verb_continuous.endswith('ing') \ and adverb_continuous in ['now', 'right now', 'at the moment', 'currently']: correct_cont = True if correct_cont: st.success("Present Continuous: Correct!") st.session_state.present_continuous_correct = True else: st.error("Present Continuous: Incorrect. Check subject-verb agreement and tense usage.") st.session_state.present_continuous_correct = False st.session_state.tt_l1_t1_3_error_count += 1 # 7) Show "Practice" and "Next" if BOTH are correct if st.session_state.get('present_simple_correct') and st.session_state.get('present_continuous_correct'): st.markdown('
    ', unsafe_allow_html=True) col_btn1, col_btn2 = st.columns(2) with col_btn1: if st.button("Practice", key='tt_l1_t1_3_practice'): navigate_to_practice_TT_L1_T1_3() st.rerun() with col_btn2: if st.button("Next", key='tt_l1_t1_3_next'): # Use your helper function to record data, e.g.: complete_task_and_record_data( page_prefix="tt_l1_t1_3", task_id="TT_L1_T1_3", next_page_func=navigate_to_task1_1 ) # 8) "Back" button if st.button("Back", key="back_to_tt_l1_t1_2_tt_l1_t1_3"): back_to_tt_l1_t1_2() st.rerun() def set_selectbox_style(element_index, color, padding): js_code = f""" """ components.html(js_code, height=0, width=0) def TT_L1_T2_1(): st.title("Quantifiers in Noun Phrases") add_css() # Initialize session states if "rule_expanded" not in st.session_state: st.session_state.rule_expanded = False if "tt_l1_t2_1_start_time" not in st.session_state: st.session_state.tt_l1_t2_1_start_time = datetime.datetime.now() if "tt_l1_t2_1_error_count" not in st.session_state: st.session_state.tt_l1_t2_1_error_count = 0 if 'tt_l1_t2_1_all_correct' not in st.session_state: st.session_state.tt_l1_t2_1_all_correct = False # Rule Section with Dropdown inside Yellow Frame st.markdown("""

    πŸ“’ Rules

    Expand to see the rule

    In this lesson, we explore quantifiers in noun phrases. Quantifiers are words that indicate the quantity of a noun. They can be used with both countable and uncountable nouns.

    Countable Nouns: These are nouns that can be counted. They have both singular and plural forms. For example, "book/books", "car/cars", "student/students", "cat/cats", "idea/ideas".

    Uncountable Nouns: These are nouns that cannot be counted. They do not have a plural form. For example, "water", "sugar", "information", "money", "time", "equipment", "knowledge".

    """.replace("{open}", "open" if st.session_state.rule_expanded else ""), unsafe_allow_html=True) col1, col2 = st.columns(2) col3, col4 = st.columns(2) with col1: quantifier_countable = st.selectbox("Quantifier (Countable)", ["many", "a few", "several", "few"], key="quantifier_countable") with col2: countable_noun = st.selectbox("Countable Noun", ["books", "cars", "cats", "dogs", "people", "students", "ideas"], key="countable_noun") with col3: quantifier_uncountable = st.selectbox("Quantifier (Uncountable)", ["some", "a lot of", "much", "little", "a little"], key="quantifier_uncountable") with col4: uncountable_noun = st.selectbox("Uncountable Noun", ["water", "sugar", "rice", "milk", "money", "time", "information", "equipment", "knowledge"], key="uncountable_noun") set_selectbox_style(0, 'darkblue', '10px') set_selectbox_style(1, 'rgba(255, 255, 0, 0.5)', '10px') set_selectbox_style(2, 'darkblue', '10px') set_selectbox_style(3, 'rgba(255, 255, 0, 0.5)', '10px') # Display constructed noun phrases st.markdown(""" ### Your Countable Noun Phrase:
    {} {}
    """.format(quantifier_countable, countable_noun), unsafe_allow_html=True) st.markdown(""" ### Your Uncountable Noun Phrase:
    {} {}
    """.format(quantifier_uncountable, uncountable_noun), unsafe_allow_html=True) st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Here are some examples of noun phrases with different quantifiers:

    Only a few students are waiting in the lecture hall.
    Dewey's theories are so complex that not many people understand them.
    I have a little money, but I think it's enough for the movie at least.
    We had little time to prepare before we had to go.
    Many universities do not have much equipment for students who are deaf or hard of hearing.
    """, unsafe_allow_html=True) # Practice Section with Drag and Drop st.markdown("""

    🎯 Practice 1: Drag and Drop Task

    Drag the nouns into the correct columns: Countable or Uncountable.

    """, unsafe_allow_html=True) # Drag-and-Drop Component drag_and_drop_css = """ """ drag_and_drop_js = """

    Drag and Drop Task: Separate Nouns

    books
    water
    rice
    cars
    sugar
    cats
    people
    students
    money
    time
    information
    ideas
    knowledge
    equipment
    Countable
    Uncountable

    """ components.html(drag_and_drop_css + drag_and_drop_js, height=700) # Practice Section: Multiple-Choice Questions st.markdown("""

    🎯 Practice 2: Select the Correct Quantifier

    Select the appropriate quantifier to complete the sentences.

    """, unsafe_allow_html=True) # Questions and Answers questions = [ { 'question': 'Only ____________ students are waiting in the lecture hall.', 'options': ['few', 'a few', 'little', 'a little'], 'answer': 'a few' }, { 'question': 'Dewey’s theories are so complex that not _____________ people understand them.', 'options': ['much', 'many', 'little', 'a little'], 'answer': 'many' }, { 'question': 'I have ______________ money, but I think it is enough for the movie at least.', 'options': ['few', 'a few', 'little', 'a little'], 'answer': 'a little' }, { 'question': 'We had ___________ time to prepare before we had to go. I think we are not that ready.', 'options': ['few', 'a few', 'little', 'a little'], 'answer': 'little' }, { 'question': 'Many universities do not have _____ equipment for students who are deaf or hard of hearing.', 'options': ['many', 'much', 'both', 'few'], 'answer': 'much' } ] # Streamlit Radio Buttons without default selected and checking logic user_answers = [] for idx, q in enumerate(questions): st.write(f"{idx + 1}. {q['question']}") user_answers.append(st.radio("", q['options'],index=None, key=f"mcq_{idx}")) if st.button("Check"): score = 0 for i, answer in enumerate(user_answers): if answer == questions[i]["answer"]: score += 1 if score == len(questions): st.success(f"Great job! You answered all {score} questions correctly.") st.session_state.tt_l1_t2_1_all_correct = True # Correctly update session state else: st.error(f"You got {score} out of {len(questions)} correct. Try again.") st.session_state.tt_l1_t2_1_error_count += 1 if st.session_state.tt_l1_t2_1_all_correct: col1, col2 = st.columns(2) with col1: # Here we call navigate_to_practice_TT_L1_T1_3 st.button('Practice', on_click=navigate_to_practice_TT_L1_T2_1, key='tt_l1_t2_1_practice') with col2: if st.button('Next', key='tt_l1_t2_1_next'): complete_task_and_record_data( page_prefix="tt_l1_t2_1", task_id="TT_L1_T2_1", next_page_func=navigate_to_task1_2 ) def TT_L1_T3_1(): st.title("Prepositions of Time and Place") add_css() # Initialize session states if "rule_expanded" not in st.session_state: st.session_state.rule_expanded = False if "scroll_triggered" not in st.session_state: st.session_state.scroll_triggered = False if "tt_l1_t3_1_start_time" not in st.session_state: st.session_state.tt_l1_t3_1_start_time = datetime.datetime.now() if "tt_l1_t3_1_error_count" not in st.session_state: st.session_state.tt_l1_t3_1_error_count = 0 if 'tt_l1_t3_1_all_correct' not in st.session_state: st.session_state.tt_l1_t3_1_all_correct = False # Rule Section with Dropdown inside a Yellow Frame st.markdown("""

    πŸ“’ Rules

    Expand to see the rule

    Prepositions of Time: These prepositions indicate when something happens. Common prepositions of time include:

    • in: for months, years, centuries, and long periods (e.g., in July, in 1990).
    • on: for days and dates (e.g., on Monday, on the 5th of June).
    • at: for precise times (e.g., at 7 PM, at noon).

    Prepositions of Place: These prepositions indicate the location of something. Common prepositions of place include:

    • in: for enclosed spaces (e.g., in the room, in the car).
    • on: for surfaces (e.g., on the table, on the wall).
    • at: for specific points (e.g., at the office, at the bus stop).

    The prepositional phrases are highlighted in light purple.

    Example:

    We will meet at the office .
    """.replace("{open}", "open" if st.session_state.rule_expanded else ""), unsafe_allow_html=True) # Examples Section with Dropdown inside Green Frame st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Here are some examples of sentences using prepositions of time and place:

    The cat is sleeping on the mat .
    She works at the office .
    They arrived in the afternoon .
    On Monday , we will meet at noon .
    """, unsafe_allow_html=True) # Practice Section: Multiple-Choice Questions st.markdown("""

    🎯 Practice: Select the Correct Preposition

    Fill in the blanks with the correct preposition of time or place.

    """, unsafe_allow_html=True) # Questions and Options questions = [ {"question": "We will meet ___ the office.", "options": ["at", "on", "in"], "answer": "at"}, {"question": "She has a meeting ___ Monday.", "options": ["on", "at", "in"], "answer": "on"}, {"question": "They arrived ___ the afternoon.", "options": ["in", "on", "at"], "answer": "in"}, {"question": "We usually have dinner ___ 7 PM.", "options": ["at", "on", "in"], "answer": "at"}, {"question": "He was born ___ 1990.", "options": ["in", "on", "at"], "answer": "in"}, ] # Collect user answers with radio buttons (no default selected) user_answers = [] for idx, q in enumerate(questions): st.write(f"{idx + 1}. {q['question']}") user_answers.append(st.radio("", q["options"],index=None, key=f"mcq_{idx}")) # Add a "Check" button to evaluate answers if st.button("Check"): score = sum(1 for i, answer in enumerate(user_answers) if answer == questions[i]["answer"]) st.markdown(f"### Your Score: {score}/{len(questions)}") # If all answers are correct, display a success message and "Next" button if score == len(questions): st.success("Great job! You answered all questions correctly.") st.session_state.tt_l1_t3_1_all_correct = True else: st.error("You made some mistakes. Please try again.") st.session_state.tt_l1_t3_1_error_count += 1 if st.session_state.get('tt_l1_t3_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L1_T3_1, key='tt_l1_t3_1_practice') with col2: if st.button('Next', key='tt_l1_t3_1_next'): complete_task_and_record_data( page_prefix="tt_l1_t3_1", task_id="TT_L1_T3_1", next_page_func=navigate_to_task1_3 ) def TT_L1_T4_1(): st.title("Compound Sentences with Gerunds and Infinitives") add_css() # Initialize session states if "rule_expanded" not in st.session_state: st.session_state.rule_expanded = False if 'tt_l1_t4_1_all_correct' not in st.session_state: st.session_state.tt_l1_t4_1_all_correct = False if "tt_l1_t4_1_start_time" not in st.session_state: st.session_state.tt_l1_t4_1_start_time = datetime.datetime.now() if "tt_l1_t4_1_error_count" not in st.session_state: st.session_state.tt_l1_t4_1_error_count = 0 # Rule Section with Dropdown inside a Yellow Frame st.markdown("""

    πŸ“’ Rules

    Expand to see the rule

    Gerunds: A gerund is a verb form that ends in '-ing' and functions as a noun. Some verbs are typically followed by gerunds.

    Infinitives: An infinitive is the base form of a verb, often preceded by 'to'. Some verbs are typically followed by infinitives.

    The verbs followed by gerunds or infinitives together with the second verb form a verb phrase, which is highlighted in red.

    Common Verbs Followed by Gerunds and Infinitives (A1-A2 Level)

    Verbs Followed by Gerunds Verbs Followed by Infinitives
    enjoy want
    mind need
    suggest decide
    avoid hope
    finish learn
    """.replace("{open}", "open" if st.session_state.rule_expanded else ""), unsafe_allow_html=True) # Interactive Section to Form Verb Phrases verbs_followed_by_gerunds = ["enjoy", "mind", "suggest", "avoid", "finish"] verbs_followed_by_infinitives = ["want", "need", "decide", "hope", "learn"] simple_verbs = ["do", "finish", "start", "learn", "write"] col1, col2 = st.columns(2) with col1: verb_gerund = st.selectbox("Verbs Followed by Gerunds", verbs_followed_by_gerunds, key="verb_gerund") simple_verb_gerund = st.selectbox("Simple Verbs for Gerunds", simple_verbs, key="simple_verb_gerund") gerund_result = f"{verb_gerund} {simple_verb_gerund}ing" with col2: verb_infinitive = st.selectbox("Verbs Followed by Infinitives", verbs_followed_by_infinitives, key="verb_infinitive") simple_verb_infinitive = st.selectbox("Simple Verbs for Infinitives", simple_verbs, key="simple_verb_infinitive") infinitive_result = f"{verb_infinitive} to {simple_verb_infinitive}" # Display constructed verb phrases with highlighting st.markdown(f""" ### Your Gerund Verb Phrase:
    {gerund_result}
    """, unsafe_allow_html=True) st.markdown(f""" ### Your Infinitive Verb Phrase:
    {infinitive_result}
    """, unsafe_allow_html=True) # Examples Section with Dropdown inside Green Frame st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Here are some examples of sentences with gerunds and infinitives:

    I like to swim .
    She decided to leave early.
    We enjoy reading books .
    """, unsafe_allow_html=True) # Practice Section: Multiple-Choice Questions (Gerund/Infinitive) st.markdown("""

    🎯 Practice: Choose the Correct Form

    Select the correct gerund or infinitive form to complete each sentence.

    """, unsafe_allow_html=True) # Questions and Answers sentences = [ {"question": "1. I enjoy ___ (read).", "options": ["reading", "to read"], "answer": "reading"}, {"question": "2. She wants ___ (leave) early.", "options": ["leaving", "to leave"], "answer": "to leave"}, {"question": "3. We decided ___ (go) to the park.", "options": ["going", "to go"], "answer": "to go"}, {"question": "4. He avoids ___ (talk) during meetings.", "options": ["talking", "to talk"], "answer": "talking"}, {"question": "5. They need ___ (finish) the project soon.", "options": ["finishing", "to finish"], "answer": "to finish"} ] # Store user answers user_answers = [] # Collect user answers using radio buttons (no pre-selected option) for idx, q in enumerate(sentences): st.write(q["question"]) user_answers.append(st.radio("", q["options"],index=None, key=f"mcq_{idx}")) # Add a "Check" button if st.button("Check"): score = 0 for i, question in enumerate(sentences): if user_answers[i] == question["answer"]: score += 1 st.markdown(f"### Your Score: {score}/{len(sentences)}") # If all are correct, set the session-state variable to True if score == len(sentences): st.success("Great job! You answered all questions correctly.") st.session_state['tt_l1_t4_1_all_correct'] = True else: st.error("Some answers are incorrect. Try again.") st.session_state.tt_l1_t4_1__error_count += 1 # If user answered all questions correctly, show Practice & Next buttons if st.session_state.get('tt_l1_t4_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L1_T4_1, key='tt_l1_t4_1_practice') with col2: if st.button('Next', key='tt_l1_t4_1_next'): complete_task_and_record_data( page_prefix="tt_l1_t4_1", task_id="TT_L1_T4_1", next_page_func=navigate_to_tt_l1_t4_2 ) def TT_L1_T4_2(): st.title("Compound Sentences with Gerunds and Infinitives") if 'tt_l1_t4_2_all_correct' not in st.session_state: st.session_state.tt_l1_t4_2_all_correct = False if "tt_l1_t4_2_start_time" not in st.session_state: st.session_state.tt_l1_t4_2_start_time = datetime.datetime.now() if "tt_l1_t4_2_error_count" not in st.session_state: st.session_state.tt_l1_t4_2_error_count = 0 add_css() # Rule Section st.markdown("""

    πŸ“’ Rules

    Expand to see the rule

    A compound sentence is a sentence that has at least two independent clauses joined by a coordinating conjunction. Each clause has its own subject and verb.

    The coordinating conjunctions can be remembered using the acronym FANBOYS:

    • For
    • And
    • Nor
    • But
    • Or
    • Yet
    • So

    Use a comma before the coordinating conjunction to connect the two clauses.

    For example:

    She likes reading, and he enjoys writing.

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Compound Sentence with Gerunds

    She enjoys reading books, and he loves writing stories.

    Example 2: Compound Sentence with Infinitives

    They want to play soccer, but we prefer to watch a movie.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice

    Create your own compound sentence using the dropdown lists below. Choose subjects, verbs, and actions to form a sentence with gerunds or infinitives.

    """, unsafe_allow_html=True) # Data for dropdowns subjects = ['I', 'You', 'He', 'She', 'We', 'They'] verbs_followed_by_gerunds = ['enjoy', 'avoid', 'finish', 'mind', 'suggest'] verbs_followed_by_infinitives = ['want', 'need', 'decide', 'hope', 'learn'] verbs_base = verbs_followed_by_gerunds + verbs_followed_by_infinitives verbs_singular = [verb + 's' for verb in verbs_base] actions = ['reading', 'writing', 'swimming', 'running', 'cooking', 'to read', 'to write', 'to swim', 'to run', 'to cook'] conjunctions = ['and', 'but', 'or', 'so'] col1, col2, col3, col4 = st.columns(4) with col1: subject1 = st.selectbox('Subject 1', subjects, key='subject1') with col2: if subject1 in ['He', 'She']: verbs_list1 = verbs_singular else: verbs_list1 = verbs_base verb1 = st.selectbox('Verb 1', verbs_list1, key='verb1') with col3: action1 = st.selectbox('Action 1', actions, key='action1') with col4: conjunction = st.selectbox('Conjunction', conjunctions, key='conjunction') # Second clause col5, col6, col7 = st.columns(3) with col5: subject2 = st.selectbox('Subject 2', subjects, key='subject2') with col6: if subject2 in ['He', 'She']: verbs_list2 = verbs_singular else: verbs_list2 = verbs_base verb2 = st.selectbox('Verb 2', verbs_list2, key='verb2') with col7: action2 = st.selectbox('Action 2', actions, key='action2') # Construct the sentence sentence = f"{subject1} {verb1} {action1} ,{conjunction} {subject2} {verb2} {action2}." st.markdown(f""" ### Your Sentence
    {sentence}
    """, unsafe_allow_html=True) # Implement checking logic if st.button('Check'): # Mapping verbs to their correct forms verb_forms = { 'enjoy': 'gerund', 'avoid': 'gerund', 'finish': 'gerund', 'mind': 'gerund', 'suggest': 'gerund', 'want': 'infinitive', 'need': 'infinitive', 'decide': 'infinitive', 'hope': 'infinitive', 'learn': 'infinitive' } # Function to get base form of the verb def get_base_verb(verb): if verb.endswith('s'): return verb[:-1] return verb # Check if verb1 matches action1 verb1_base = get_base_verb(verb1) required_form1 = verb_forms.get(verb1_base, None) if required_form1 == 'gerund' and action1.endswith('ing'): clause1_correct = True elif required_form1 == 'infinitive' and action1.startswith('to '): clause1_correct = True else: clause1_correct = False # Check if verb2 matches action2 verb2_base = get_base_verb(verb2) required_form2 = verb_forms.get(verb2_base, None) if required_form2 == 'gerund' and action2.endswith('ing'): clause2_correct = True elif required_form2 == 'infinitive' and action2.startswith('to '): clause2_correct = True else: clause2_correct = False if clause1_correct and clause2_correct: st.success("Great job! Your sentence is correct.") st.session_state.tt_l1_t4_2_all_correct = True else: st.error("There seems to be a mistake in your sentence. Please check the verb and action combinations.") st.session_state.tt_l1_t4_2_error_count += 1 if st.session_state.get('tt_l1_t4_2_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L1_T4_2, key='tt_l1_t4_2_practice') with col2: st.button('Next',key='tt_l1_t4_2_next') complete_task_and_record_data( page_prefix="tt_l1_t4_2", task_id="TT_L1_T4_2", next_page_func=navigate_to_task1_4 ) if st.button("Back",key='tt_l1_t4_1_back'): back_to_tt_l1_t4_1 st.rerun() def TT_L1_T5_1(): st.title("Past Simple and Past Continuous") add_css() # Initialize session states if 'tt_l1_t5_1_all_correct' not in st.session_state: st.session_state.tt_l1_t5_1_all_correct = False if "tt_l1_t5_1_start_time" not in st.session_state: st.session_state.tt_l1_t5_1_start_time = datetime.datetime.now() if "tt_l1_t5_1_error_count" not in st.session_state: st.session_state.tt_l1_t5_1_error_count = 0 # Rule Section st.markdown("""

    πŸ“’ Rules

    Expand to see the rule

    In this lesson, we will explore the Past Simple and Past Continuous tenses, focusing on simple sentences and keywords.

    Past Simple:

    • Used for actions that happened and were completed in the past.
    • Keywords: yesterday, last night, in 1990, two days ago.

    Example:

    She went to the market yesterday.

    Past Continuous:

    • Used for actions that were in progress at a specific time in the past.
    • Keywords: while, when, as, yesterday at 6 p.m., this time yesterday, last Saturday afternoon.

    Example:

    They were watching a movie when I called.
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Examples: Past Simple

    She went to the market yesterday.

    Examples: Past Continuous

    They were watching a movie when I called.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Verb Tense

    Choose the correct form of the verb to complete the sentence:

    """, unsafe_allow_html=True) # Questions and Answers questions = [ {"question": "1. She ___ (go) to the market yesterday.", "options": ["went", "was going"], "answer": "went"}, {"question": "2. She ___ (walk) to the park yesterday at 6 p.m.", "options": ["walked", "was walking"], "answer": "was walking"}, {"question": "3. I ___ (see) him at the party last night.", "options": ["saw", "was seeing"], "answer": "saw"}, {"question": "4. During the winter of last year, Mary _____ as an intern at a startup company.", "options": ["worked", "was working"], "answer": "worked"}, {"question": "5. My friends and I ____ an English mid-term exam a few days ago.", "options": ["were taking", "took"], "answer": "took"} ] # Store user answers user_answers = [] # Display each question with radio buttons and no pre-selected option for idx, q in enumerate(questions): st.write(q["question"]) user_answers.append(st.radio("", q["options"], index=None, key=f"mcq_{idx}")) # Add a "Check" button if st.button("Check"): score = sum(1 for i, answer in enumerate(user_answers) if answer == questions[i]["answer"]) st.markdown(f"### Your Score: {score}/{len(questions)}") # If all answers are correct, display a success message if score == len(questions): st.success("Great job! You answered all questions correctly.") st.session_state.tt_l1_t5_1_all_correct = True else: st.error("You made some mistakes. Please review the rule section and try again.") st.session_state.tt_l1_t5_1_error_count += 1 # If user answered all correctly, show Practice & Next buttons if st.session_state.get('tt_l1_t5_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L1_T5_2, key='tt_l1_t5_1_practice') with col2: if st.button('Next', key='tt_l1_t5_1_next'): complete_task_and_record_data( page_prefix="tt_l1_t5_1", task_id="tt_l1_t5_1", next_page_func=navigate_to_tt_l1_t5_2 ) def TT_L1_T5_2(): st.title("Past Simple and Past Continuous with 'When' and 'While'") # Apply custom CSS add_css() # Adjust CSS for commas in the examples st.markdown(""" """, unsafe_allow_html=True) if "tt_l1_t5_2_start_time" not in st.session_state: st.session_state.tt_l1_t5_2_start_time = datetime.datetime.now() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    In this lesson, we will explore the difference between using "when" and "while" in sentences with Past Simple and Past Continuous tenses.

    When:

    • "When" is typically used to introduce an action in the Past Simple that interrupts another action that was in progress in the Past Continuous.
    • For example:
      I was reading when the phone rang.
    • Alternative structure:
      When the phone rang, I was reading.

    While:

    • "While" is used to indicate that two actions were happening simultaneously in the past, typically with both actions in the Past Continuous tense.
    • For example:
      She was cooking while he was reading.
    • Alternative structure:
      While he was reading, she was cooking.

    Comma Usage: When the subordinate clause (introduced by "when" or "while") comes before the main clause, a comma is used to separate them. If the main clause comes first, a comma is not necessary.

    """, unsafe_allow_html=True) # Example Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: "When" in a Complex Sentence

    I was reading when the phone rang.
    When the phone rang, I was reading.

    Example 2: "While" in a Complex Sentence

    She was cooking while he was reading.
    While he was reading, she was cooking.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Match the Sentence Halves

    Drag and drop to match the first half of the sentence with the correct second half.

    """, unsafe_allow_html=True) # CSS for Drag-and-Drop Activity drag_and_drop_css = """ """ # JavaScript and HTML for Drag-and-Drop Activity with modifications drag_and_drop_js = """
    Dean was finishing his last essay question on the history final exam
    What were you doing
    Nina did not hear her phone ringing
    The waiter was serving a lot of customers

    """ result = components.html(drag_and_drop_css + drag_and_drop_js, height=700, scrolling=True) # Add 'More Practice' and 'Next' buttons col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L1_T5_2, key='tt_l1_t5_2_practice') with col2: if st.button("Next", key='tt_l1_t5_2_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l1_t5_2_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L1_T5_2" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l1_t5_2_start_time = None navigate_to_task1_5() if st.button("Back", key='tt_l1_t5_2_back'): back_to_tt_l1_t5_1() st.rerun() def TT_L1_T6_1(): st.title("Future Simple vs. To Be Going To") add_css() if 'tt_l1_t6_1_all_correct' not in st.session_state: st.session_state.tt_l1_t6_1_all_correct = False if "tt_l1_t6_1_start_time" not in st.session_state: st.session_state.tt_l1_t6_1_start_time = datetime.datetime.now() if "tt_l1_t6_1_error_count" not in st.session_state: st.session_state.tt_l1_t6_1_error_count = 0 # Adjust CSS for examples and font sizes st.markdown(""" """, unsafe_allow_html=True) # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Future Simple ("will"):

    • Used for decisions made at the moment of speaking.
      Example: "I'm thirsty. I will get a glass of water."
    • Used for predictions or future facts.
      Example: "I think it will rain tomorrow."

    "To be going to":

    • Used for plans or intentions made before the moment of speaking.
      Example: "She is going to start a new job next week."
    • Used for predictions based on current evidence.
      Example: "Look at those dark clouds! It is going to rain."
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Future Simple ("will") Examples

    She will call .
    I think it will snow tomorrow.

    "To Be Going To" Examples

    They are going to travel next month.
    Look! He is going to fall!
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Choose the Correct Form

    For each sentence, choose the correct form of the verb ("will" or "to be going to") according to the rules.

    """, unsafe_allow_html=True) # Questions and Answers questions = [ {"question": "1. I feel hungry. I ___ (make) a sandwich.", "options": ["will make", "am going to make"], "answer": "will make"}, {"question": "2. Look at those clouds! It ___ (rain).", "options": ["will rain", "is going to rain"], "answer": "is going to rain"}, {"question": "3. She ___ (start) university next month; she applied last year.", "options": ["will start", "is going to start"], "answer": "is going to start"}, {"question": "4. They think the team ___ (win) the match.", "options": ["will win", "is going to win"], "answer": "will win"}, {"question": "5. He forgot his wallet. Don't worry, I ___ (lend) you some money.", "options": ["will lend", "am going to lend"], "answer": "will lend"}, ] # Create variables to store user answers user_answers = [] # Render the questions using Streamlit's radio buttons for idx, q in enumerate(questions): st.write(f"{q['question']}") user_answers.append( st.radio("", options=q['options'], index=None, key=f"q_{idx}") ) # Check Answers button logic if st.button("Check"): score = sum(1 for idx, q in enumerate(questions) if user_answers[idx] == q["answer"]) st.markdown(f"### Your Score: {score}/{len(questions)}") if score == len(questions): st.success("Great job! You answered all questions correctly.") st.session_state.tt_l1_t6_1_all_correct = True else: st.error(f"You got {score}/{len(questions)} correct. Please review the rule section above.") st.session_state.tt_l1_t6_1_error_count += 1 if st.session_state.get('tt_l1_t6_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L1_T6_1, key='tt_l1_t6_1_practice') with col2: if st.button('Next', key='tt_l1_t6_1_next'): complete_task_and_record_data( page_prefix="tt_l1_t6_1", task_id="TT_L1_T6_1", next_page_func=navigate_to_task1_6 ) def TT_L1_T7_1(): st.title("Passive Voice: Present Simple Passive and Past Simple Passive") if "tt_l1_t7_1_start_time" not in st.session_state: st.session_state.tt_l1_t7_1_start_time = datetime.datetime.now() add_css() # Add CSS to fix spacing in the Examples section st.markdown(""" """, unsafe_allow_html=True) # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    In this lesson, we explore the Passive Voice in Present Simple and Past Simple tenses. Passive voice is used when the focus is on the action, not on who or what is performing the action.

    Active Voice: The subject performs the action.

    Example: The children eat the cake.

    Passive Voice: The object of the action becomes the subject of the sentence.

    Example: The cake is eaten by the children.

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Here are some examples of sentences in active and passive voice:

    Present Simple Passive

    Letters are written by her every day.

    Past Simple Passive

    Football was played by him yesterday.

    Active Voice Examples

    She writes letters every day.
    He played football yesterday.
    They are watching a movie now.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Drag and Drop Task

    Drag the sentences into the correct columns: Active Voice or Passive Voice.

    """, unsafe_allow_html=True) # CSS for Drag-and-Drop Component drag_and_drop_css = """ """ # JavaScript for Drag-and-Drop Functionality with modifications drag_and_drop_js = """

    Drag and Drop Task: Separate Sentences

    Active Voice

    Passive Voice

    """ # Combine CSS and JS combined_html = drag_and_drop_css + drag_and_drop_js # Embed the HTML (drag-and-drop interface) components.html(combined_html, height=700, scrolling=True) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L1_T7_1, key='tt_l1_t7_1_practice') with col2: st.button('Next', key='tt_l1_t7_1_next') end_time = datetime.datetime.now() start_time = st.session_state.tt_l1_t7_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L1_T7_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l1_t7_1_start_time = None navigate_to_task1_7() def TT_L1_T8_1(): st.title("Modals: May, Might, Should, Must, Have to") if 'tt_l1_t8_1_all_correct' not in st.session_state: st.session_state.tt_l1_t2_1_all_correct = False if "tt_l1_t8_1_start_time" not in st.session_state: st.session_state.tt_l1_t8_1_start_time = datetime.datetime.now() if "tt_l1_t8_1_error_count" not in st.session_state: st.session_state.tt_l1_t8_1_error_count = 0 add_css() # Add CSS for verb phrase highlighting st.markdown(""" """, unsafe_allow_html=True) # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Can: Ability.

    Example: "She can play the piano beautifully."

    Must: Obligation and necessity.

    Example: "You must finish your homework before going out."

    Have to: Obligations coming from outside the speaker.

    Example: "I have to attend a meeting at 9 am tomorrow."

    May: Possibility referring to the present and the future.

    Example: "We may visit the museum if we have time."

    Might: Weak possibility.

    Example: "She might come to the party, but she's not sure."

    Should: Suggestions and advice.

    Example: "You should see a doctor about that cough."

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples
    • I cannot come to see you.
    • I can cook.
    • You must not wear a white shirt.
    • I mustn't be late.
    • This weekend I have to go to a party.
    • About the transport, you do not have to worry as my dad can drop us at the sports centre.
    • I think it may be dirty.
    • My dad isn't working that day; he might take us.
    • The weather might be hot and sunny.
    • You should take some money because the ticket costs Β£4.00.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Choose the Correct Modal Verb

    Select the correct modal verb to complete each sentence.

    """, unsafe_allow_html=True) # Questions and Answers questions = [ {"question": "1. I ___ swim very well.", "options": ["can", "must", "should"], "answer": "can"}, {"question": "2. You ___ finish your homework before going out.", "options": ["might", "must", "may"], "answer": "must"}, {"question": "3. She ___ visit us if she has time.", "options": ["must", "should", "may"], "answer": "may"}, {"question": "4. They ___ see a doctor about that cough.", "options": ["should", "must", "can"], "answer": "should"}, {"question": "5. He ___ attend the meeting at 9 am tomorrow.", "options": ["has to", "might", "can"], "answer": "has to"}, {"question": "6. She ___ come to the party, but she's not sure.", "options": ["may", "might", "must"], "answer": "might"}, ] user_answers = [] for idx, q in enumerate(questions): st.write(q["question"]) user_answers.append( st.radio("", options=q['options'],index=None, key=f"q_{idx}") ) if st.button("Check Answers"): score = sum(1 for idx, q in enumerate(questions) if user_answers[idx] == q['answer']) st.markdown(f"### Your Score: {score}/{len(questions)}") if score == len(questions): st.success("Great job! You answered all questions correctly.") st.session_state.tt_l1_t8_1_all_correct = True else: st.error("Some of your answers are incorrect. Please review the rule section and try again.") st.session_state.tt_l1_t8_1_all_correct = False if st.session_state.get('tt_l1_t8_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L1_T8_1, key='tt_l1_t8_1_practice') with col2: if st.button('Next', key='tt_l1_t8_1_next'): complete_task_and_record_data( page_prefix="tt_l1_t8_1", task_id="TT_L1_T8_1", next_page_func=navigate_to_task1_8 ) def TT_L2_T1_1(): st.title("TT_L1_T1_1: Introduction") add_css() if "tt_l2_t1_1_start_time" not in st.session_state: st.session_state.tt_l2_t1_1_start_time = datetime.datetime.now() # Existing content for TT_L1_T1_1() st.markdown("""

    πŸ“š Understanding Parts of Speech

    Use the dropdown lists below to explore different parts of speech.

    """, unsafe_allow_html=True) # Part of Speech Selection col1, col2, col3, col4, col5 = st.columns(5) with col1: selected_determiner = st.selectbox('Determiner', ['a', 'the', 'some', 'any'], key='selectbox1') with col2: selected_noun = st.selectbox('Noun', ['car', 'dog', 'house', 'book'], key='selectbox2') with col3: selected_verb = st.selectbox('Verb', ['run', 'jump', 'swim', 'read'], key='selectbox3') with col4: selected_adjective = st.selectbox('Adjective', ['red', 'big', 'quick', 'blue'], key='selectbox4') with col5: selected_adverb = st.selectbox('Adverb', ['quickly', 'silently', 'well', 'badly'], key='selectbox5') # Understanding Phrases Section with updated yellow color st.markdown("""

    πŸ“š Understanding Noun Phrases and Verb Phrases

    A noun phrase (yellow) is a group of words that functions as a noun in a sentence. It typically includes a noun and its modifiers.

    A verb phrase (red) consists of a verb and any accompanying words, such as auxiliaries, complements, or modifiers.

    Noun Phrases

    a car
    the red house

    Verb Phrases

    run
    is running
    run quickly
    """, unsafe_allow_html=True) # Examples Section inside Green Frame with updated yellow color st.markdown("""

    πŸ“ Examples

    The manager
    is reviewing
    the report.
    The students
    are studying
    for the exam.
    """, unsafe_allow_html=True) # Practice Section with Drag and Drop (Kept as is) st.markdown("""

    🎯 Practice

    Practice combining noun and verb phrases by dragging and dropping them into the correct order to form sentences related to work and studies.

    """, unsafe_allow_html=True) # Drag-and-Drop Interface drag_and_drop_css = """ """ # Updated Drag-and-Drop JS without the "Next" button and with page reload on correct answers drag_and_drop_js = """

    Arrange the phrases to form the correct sentences

    the project.
    The manager
    is giving
    The professor
    is working on
    the report.
    a lecture.
    is reviewing
    The team

    """ # Combine CSS and JS combined_html = drag_and_drop_css + drag_and_drop_js # Embed the HTML (drag-and-drop interface) components.html(combined_html, height=500, scrolling=True) if st.button("Next", key='tt_l2_t1_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t1_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T1_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t1_1_start_time = None navigate_to_tt_l2_t1_2() st.rerun() def TT_L2_T1_2(): st.title("Zero and First Conditional Sentences") if "tt_l2_t1_2_start_time" not in st.session_state: st.session_state.tt_l2_t1_2_start_time = datetime.datetime.now() add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Understanding Clauses: In English sentences, a clause is a group of words that contains a subject and a verb. Clauses can be independent (main clauses) or dependent (subordinate clauses).

    Main Clause: A clause that can stand alone as a sentence. It contains a subject and a verb and expresses a complete thought.

    Example: I read the book.

    Subordinate Clause: A clause that cannot stand alone as a sentence. It provides additional information to the main clause.

    Example: If I read the book, I will understand the topic better.

    Zero Conditional

    The Zero Conditional is used to talk about things that are always true or very likely to happen. The structure is:

    • Form: Present Simple 'if' clause, real conditions.
    • Example: If you heat water, it boils.

    First Conditional

    The First Conditional is used to talk about a likely or possible result in the future. The structure is:

    • Form: Present Simple 'if' clause + 'will', future, likely outcome.
    • Example: If it rains, we will stay home.
    • Form: Present Simple 'if' clause + Modal, future, possible outcome.
    • Example: If you study, you might pass the exam.
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Zero Conditional

    If you heat water, it boils.

    Example 2: First Conditional

    If you study, you might pass the exam.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Select the Correct Verb

    Select the appropriate verb to complete the conditional sentences.

    """, unsafe_allow_html=True) # HTML and JavaScript for the updated practice task practice_html = """

    1. If I more time, I a new skill.

    2. If they more focused, they the project on time.

    3. If water at 100Β°C, it into steam.

    4. If you ice, it .

    5. If he harder, he the test.

    """ # Render the HTML and JavaScript using Streamlit's components.html component_value = components.html(practice_html, height=400) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T1_2, key='tt_l2_t1_2_practice') with col2: if st.button('Next', key='tt_l2_t1_2_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t1_2_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T1_2" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t1_2_start_time = None navigate_to_tt_l2_t1_3() st.rerun() if st.button('Back', key='tt_l2_t1_2_back'): back_to_tt_l2_t1_1() st.rerun() def TT_L2_T1_3(): st.title("Zero and First Conditional Sentences") if "tt_l2_t1_3_start_time" not in st.session_state: st.session_state.tt_l2_t1_3_start_time = datetime.datetime.now() add_css() # Add CSS to fix spacing in the Examples section st.markdown(""" """, unsafe_allow_html=True) # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Zero Conditional: The Zero Conditional is used for things that are always true or very likely to happen. It refers to general truths or facts. It’s formed using the Present Simple tense in both the 'if' clause and the main clause.

    Example: If you boil water, it turns into steam.

    First Conditional: The First Conditional is used to talk about likely or possible results in the future. It’s formed using the Present Simple tense in the 'if' clause and 'will' or a modal verb in the main clause.

    Example: If it rains tomorrow, we will cancel the picnic.

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Zero Conditional

    If you heat ice, it melts.

    First Conditional

    If you study, you will pass the exam.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Match the Sentence Halves

    Drag and drop to match the first half of the sentence with the correct second half.

    """, unsafe_allow_html=True) # CSS for Drag-and-Drop Component drag_and_drop_css = """ """ # JavaScript for Drag-and-Drop Functionality with modifications drag_and_drop_js = """

    """ components.html(drag_and_drop_css + drag_and_drop_js, height=700) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T1_3, key='tt_l2_t1_3_practice') with col2: if st.button('Next', key='tt_l2_t1_3_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t1_3_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T1_3" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t1_3_start_time = None navigate_to_task2_1() st.rerun() if st.button('Back', key='tt_l2_t1_3_back'): back_to_tt_l2_t1_2() st.rerun() def TT_L2_T2_1(): st.title("Present Perfect vs Past Simple") if "tt_l2_t2_1_start_time" not in st.session_state: st.session_state.tt_l2_t2_1_start_time = datetime.datetime.now() add_css() # Fix spacing in the Examples section st.markdown(""" """, unsafe_allow_html=True) # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Present Perfect: The Present Perfect is used to talk about actions that happened at an unspecified time in the past, often focusing on the result of the action or its connection to the present. It is formed using 'have/has' + the past participle of the verb.

    Key Words: ever, never, already, just, yet, for, since

    Example: She has already visited Paris.


    Past Simple: The Past Simple is used to talk about actions that happened at a specific time in the past. It is formed using the base form of the verb + 'ed' for regular verbs or the second form for irregular verbs.

    Key Words: yesterday, last week, ago, in 2010

    Example: She visited Paris last year.

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Present Perfect

    They have lived in this house since 2004.

    Past Simple

    They moved to a new house last year.
    """, unsafe_allow_html=True) # Practice Section: Drag and Drop st.markdown("""

    🎯 Practice: Drag and Drop to Complete Sentences

    Drag and drop the correct verb forms into the blanks to complete the sentences.

    """, unsafe_allow_html=True) # CSS for Drag-and-Drop Component with Adjusted Drop Zones drag_and_drop_css = """ """ # JavaScript for Drag-and-Drop Logic drag_and_drop_js = """
    have stuck
    stuck
    have known
    knew
    has just finished
    just has finished
    have been
    have never been
    lived
    has lived
    1. I
    in the traffic jam for at least two hours.
    2. We
    each other since we moved to Bangkok.
    3. Britney
    her Judo class. She must be very tired now.
    4. I
    to Puerto Rico before. This is my very first time.
    5. She
    in Bangkok in 2022.

    """ # Combine and Render Component components.html(drag_and_drop_css + drag_and_drop_js, height=800) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T2_1, key='tt_l2_t2_1_practice') with col2: if st.button('Next', on_click=navigate_to_task2_2, key='tt_l2_t2_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t2_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T2_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t2_1_start_time = None navigate_to_task2_2() st.rerun() def TT_L2_T3_1(): st.title("Gerunds and Infinitives") if 'tt_l2_t3_1_all_correct' not in st.session_state: st.session_state.tt_l2_t2_1_all_correct = False if "tt_l2_t3_1_start_time" not in st.session_state: st.session_state.tt_l2_t3_1_start_time = datetime.datetime.now() if "tt_l2_t3_1_error_count" not in st.session_state: st.session_state.tt_l2_t3_1_error_count = 0 add_css() # Fix spacing in the Examples section st.markdown(""" """, unsafe_allow_html=True) # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Understanding Gerunds and Infinitives:

    A gerund is a verb form that ends in -ing and functions as a noun in a sentence.

    An infinitive is the base form of a verb, often preceded by to, and can function as a noun, adjective, or adverb.

    When to Use Gerunds:

    • After certain verbs (e.g., enjoy, mind, avoid): e.g., I enjoy reading books.
    • As the subject of a sentence: e.g., Swimming is fun.

    When to Use Infinitives:

    • After certain verbs (e.g., decide, promise, afford): e.g., She decided to leave early.
    • After adjectives: e.g., It's easy to learn English.
    • To express purpose: e.g., He went to the store to buy milk.

    Special Cases:

    • After verbs like make and let, use the base form of the verb without to: e.g., They let him leave early.
    • Some verbs can be followed by either a gerund or an infinitive, sometimes with a change in meaning.
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Here are some examples of sentences with gerunds and infinitives:

    I enjoy reading books.
    She decided to leave early.
    They let him leave early.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Choose the Correct Form

    Select the correct form of the verb to complete each sentence.

    """, unsafe_allow_html=True) # Questions and Answers questions = [ {"question": "1. So would you mind ___ Mr. and Mrs. Thomas.", "options": ["telling", "to tell", "tell"], "answer": "telling"}, {"question": "2. I would like ___ there a few years to finish studying and to have my own money to start thinking of traveling.", "options": ["stay", "staying", "to stay"], "answer": "to stay"}, {"question": "3. She makes me ___ when I'm sad.", "options": ["to smile", "smile", "smiling"], "answer": "smile"}, {"question": "4. I would like you ___ to a picnic on Saturday.", "options": ["to come", "coming", "come"], "answer": "to come"}, {"question": "5. Your parents want me ___ on holiday with them this summer.", "options": ["going", "to go", "go"], "answer": "to go"}, ] user_answers = [] for idx, q in enumerate(questions): st.write(q["question"]) user_answer = st.radio("", q["options"], key=f"q_{idx}", index=None) user_answers.append(user_answer) if st.button("Check Answers"): score = 0 total = len(questions) for idx, q in enumerate(questions): if user_answers[idx] == q["answer"]: score += 1 else: score += 0 st.write(f"**Your Score: {score}/{total}**") if score == total: st.success(f"Great job! You answered all {score} questions correctly.") st.session_state.tt_l2_t3_1_all_correct = True # Correctly update session state else: st.error(f"You got {score} out of {len(questions)} correct. Try again.") st.session_state.tt_l2_t3_1_error_count += 1 if st.session_state.get('tt_l2_t3_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T3_1, key='tt_l2_t3_1_practice') with col2: if st.button('Next', key='tt_l2_t3_1_next'): complete_task_and_record_data( page_prefix="tt_l2_t3_1", task_id="TT_L2_T3_1", next_page_func=navigate_to_tt_l2_t3_2 ) def TT_L2_T3_2(): st.title("Gerunds and Infinitives in Compound Sentences") if "tt_l2_t3_2_start_time" not in st.session_state: st.session_state.tt_l2_t3_2_start_time = datetime.datetime.now() add_css() # Fix spacing and alignment in the Examples section st.markdown(""" """, unsafe_allow_html=True) # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    In this lesson, we focus on compound sentences. A compound sentence consists of two independent clauses joined by a coordinating conjunction (e.g., 'and', 'but', 'or'). Each clause contains its own subject and verb.

    Here are some examples:

    Example: She enjoys reading books, and he doesn’t mind writing stories.

    • Gerunds: These are verb forms that end in '-ing' and function as nouns.
    • Infinitives: Infinitives are base verbs often preceded by 'to' (e.g., to read, to write).
    """, unsafe_allow_html=True) # Example Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Compound Sentence with Gerund

    She enjoys reading books , and he doesn't mind writing stories.

    Example 2: Compound Sentence with Infinitive

    They want to play soccer , but we decided to watch a movie.
    """, unsafe_allow_html=True) # Practice Section: Construct Compound Sentences st.markdown("""

    🎯 Practice: Construct Compound Sentences

    Create compound sentences using the dropdown lists below. The sentences should correctly match subjects with verbs, gerunds, and infinitives.

    """, unsafe_allow_html=True) # Dropdown options for constructing sentences with more distractors subjects = ['She', 'He', 'They', 'We', 'I'] subjects2 = ['she', 'he', 'they', 'we', 'I'] conjunctions = ['and', 'but', 'or'] verbs_followed_by_gerunds_singular = ['enjoys', "doesn't mind", 'suggests', 'recommends', 'avoids', 'likes'] verbs_followed_by_gerunds_plural = ['enjoy', "don't mind", 'suggest', 'recommend', 'avoid', 'like'] verbs_followed_by_infinitives_singular = ['wants', 'needs', 'decides', 'hopes', 'expects', 'plans'] verbs_followed_by_infinitives_plural = ['want', 'need', 'decide', 'hope', 'expect', 'plan'] gerunds = ['reading', 'writing', 'swimming', 'running', 'cooking', 'dancing'] infinitives = ['to read', 'to write', 'to swim', 'to run', 'to cook', 'to dance'] col1, col2, col3, col4, col5 = st.columns(5) with col1: subject1 = st.selectbox('Subject 1', subjects, key='subject1') with col2: if subject1 in ['She', 'He']: verb_gerund = st.selectbox('Verb (Gerund)', verbs_followed_by_gerunds_singular, key='verb_gerund') else: verb_gerund = st.selectbox('Verb (Gerund)', verbs_followed_by_gerunds_plural, key='verb_gerund') with col3: gerund = st.selectbox('Gerund', gerunds, key='gerund') with col4: conjunction = st.selectbox('Conjunction', conjunctions, key='conjunction') with col5: subject2 = st.selectbox('Subject 2', subjects2, key='subject2') if subject2.capitalize() in ['She', 'He']: verb_infinitive = st.selectbox('Verb (Infinitive)', verbs_followed_by_infinitives_singular, key='verb_infinitive') else: verb_infinitive = st.selectbox('Verb (Infinitive)', verbs_followed_by_infinitives_plural, key='verb_infinitive') infinitive = st.selectbox('Infinitive', infinitives, key='infinitive') # Constructed sentence gerund_sentence = f"{subject1} {verb_gerund} {gerund}, {conjunction} {subject2} {verb_infinitive} {infinitive}." st.markdown(f""" ### Your Sentence
    {gerund_sentence}
    """, unsafe_allow_html=True) # Checking logic for the constructed sentence correct_sentences = [ f"{subject1} {verb_gerund} {gerund}, {conjunction} {subject2} {verb_infinitive} {infinitive}." ] # You can add more valid combinations to the list if needed. if st.button("Check Sentence"): if gerund_sentence in correct_sentences: st.success("Great job! Your sentence is correct.") else: st.error("There might be an issue with your sentence. Please check the verb forms and subjects.") # Final Practice: Drag-and-Drop Task to Match Compound Sentences st.markdown("""

    🎯 Practice: Match the Sentence Halves

    Drag the correct sentence half into the appropriate blank to complete the compound sentences.

    """, unsafe_allow_html=True) # CSS for Drag-and-Drop Component drag_and_drop_css = """ """ # JavaScript for Drag-and-Drop Functionality with modifications drag_and_drop_js = """
    As soon as the bus arrived,
    Rich people could become poor,
    Because of the Online system in convenient stores,
    Although the bus has arrived,

    """ components.html(drag_and_drop_css + drag_and_drop_js, height=700) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T3_2, key='tt_l2_t3_2_practice') with col2: if st.button('Next', key='tt_l2_t3_2_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t3_2_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T3_2" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t3_2_start_time = None navigate_to_task2_3() st.rerun() if st.button('Back', key='tt_l2_t3_2_back'): back_to_tt_l2_t3_1() st.rerun() def TT_L2_T4_1(): st.title("Complex Sentences with Relative Pronouns") if 'tt_l2_t4_1_all_correct' not in st.session_state: st.session_state.tt_l2_t4_1_all_correct = False if "tt_l2_t4_1_start_time" not in st.session_state: st.session_state.tt_l2_t4_1_start_time = datetime.datetime.now() if "tt_l2_t4_1_error_count" not in st.session_state: st.session_state.tt_l2_t4_1_error_count = 0 add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Relative Clauses: Relative clauses are used to give additional information about a noun. They start with a relative pronoun and function as an adjective.

    Defining Clauses:

    • Who/That: Used to define people. For example, "This is the person who helped me."
    • Which/That: Used to define things. For example, "The book which I read."

    Place Clauses:

    • Where: Used to define places. For example, "This is the house where I live."
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Defining Clause with "Who"

    This is the person who helped me.

    Example 2: Defining Clause with "Which"

    The book which I read was interesting.

    Example 3: Place Clause with "Where"

    This is the house where I live.
    """, unsafe_allow_html=True) # Practice Section st.markdown("""

    🎯 Practice: Choose the Correct Relative Pronoun

    Select the correct relative pronoun to complete each sentence.

    """, unsafe_allow_html=True) # Questions and Answers questions = [ {"question": "1. The busiest time for stores is before Christmas, __________ there are sales and shoppers everywhere.", "options": ["where", "who", "when"], "answer": "when"}, {"question": "2. The beautiful garden reminded him of the house __________ he used to live in.", "options": ["where", "who", "which"], "answer": "where"}, {"question": "3. The customer gave the message to the secretary, __________ was supposed to pass it on to her boss.", "options": ["who", "which", "that"], "answer": "who"}, {"question": "4. The students are studying hard for their exam, __________ is taking place the next morning.", "options": ["where", "who", "which"], "answer": "which"}, {"question": "5. The vitamins __________ we brought from England last year are very expensive in Thailand.", "options": ["that", "where", "at which"], "answer": "that"}, ] # Store user answers user_answers = [] # Display each question with radio buttons and no pre-selected option (index=None) for idx, q in enumerate(questions): st.write(q["question"]) user_answer = st.radio("", q["options"], index=None, key=f"q_{idx}") user_answers.append(user_answer) # Add a "Check Answers" button if st.button("Check Answers"): score = sum(1 for idx, q in enumerate(questions) if user_answers[idx] == q["answer"]) st.markdown(f"### Your Score: {score}/{len(questions)}") if score == len(questions): st.success("Great job! You answered all questions correctly.") st.session_state.tt_l2_t4_1_all_correct = True # Correctly update session state else: st.error("You made some mistakes. Please review the rule section above.") st.session_state.tt_l2_t4_1_error_count += 1 if st.session_state.get('tt_l2_t4_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T4_1, key='tt_l2_t4_1_practice') with col2: if st.button('Next', key='tt_l2_t4_1_next'): complete_task_and_record_data( page_prefix="tt_l2_t4_1", task_id="TT_L2_T4_1", next_page_func=navigate_to_task2_4 ) def TT_L2_T5_1(): st.title("Complex Sentences with Subordinated Clauses") if 'tt_l2_t5_1_all_correct' not in st.session_state: st.session_state.tt_l2_t5_1_all_correct = False if "tt_l2_t5_1_start_time" not in st.session_state: st.session_state.tt_l2_t5_1_start_time = datetime.datetime.now() if "tt_l2_t5_1_error_count" not in st.session_state: st.session_state.tt_l2_t5_1_error_count = 0 add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Subordinate Clauses: Subordinate clauses add detail to the main clause and begin with subordinating conjunctions such as "because," "when," "since," etc.

    • Reason and Contrast: A finite clause introduced by conjunctions 'because', 'since', 'although', etc. Example: "She was tired because she stayed up late."
    • Time Clauses: A subordinate clause introduced by 'before', 'after', 'as soon as'. Example: "As soon as the bus arrivedd, everyone rushed to get on it."

    Comma Usage in Subordinate Clauses:

    If a subordinate clause precedes the main clause, use a comma after the subordinate clause. Example: "Although it was raining, she went for a walk."

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Time Clause (Subordinate Clause before Main Clause)

    As soon as the bus arrived, everyone rushed to get on it.

    Example 2: Reason Clause (Main Clause before Subordinate Clause)

    Rich people could become poor, because they spend more than they earn.

    Example 3: Contrast Clause (Subordinate Clause before Main Clause)

    Although the bus has arrived, I will wait outside for fresh air.

    Example 4: Reason Clause (Main Clause before Subordinate Clause)

    People use online systems in stores, because it is convenient for paying bills.
    """, unsafe_allow_html=True) # Practice Section: Drag and Drop Task st.markdown("""

    🎯 Practice: Match the Sentence Halves

    Drag and drop to match the first half of the sentence (main clause) with the correct second half (subordinate clause).

    """, unsafe_allow_html=True) # Drag-and-Drop Component drag_and_drop_css = """ """ drag_and_drop_js = """
    As soon as the bus arrived,
    Rich people could become poor
    Because of the online system in convenience stores,
    Although the bus has arrived,
    everyone rushed to get on it because the rain was pouring heavily.
    because they spend more money than they could earn.
    people can use counter services to pay all their bills.
    I will wait outside and breathe fresh air for a while.

    """ # Display the drag-and-drop component without the 'key' parameter components.html( drag_and_drop_css + drag_and_drop_js, height=900, scrolling=True ) # Practice Section: Select the Correct Conjunction st.markdown("""

    🎯 Practice: Select the Correct Conjunction

    Select the appropriate subordinating conjunction to complete the sentence correctly.

    """, unsafe_allow_html=True) # Questions and Answers sentences = [ {"sentence": "1. People do different leisure activities ___ they have different interests.", "options": ["since", "which", "when"], "answer": "since"}, {"sentence": "2. Traffic becomes a serious problem ___ more people use their own cars.", "options": ["because", "if", "while"], "answer": "because"}, {"sentence": "3. Some people know their strengths ___ others do not and are unhappy.", "options": ["while", "if", "because", "although"], "answer": "while"}, {"sentence": "4. Some students don’t get good grades ___ they work hard.", "options": ["even though", "because", "in spite of"], "answer": "even though"}, ] user_answers = [] for idx, q in enumerate(sentences): st.write(q["sentence"]) user_answer = st.selectbox("", q["options"], index=0, key=f"select_{idx}") user_answers.append(user_answer) if st.button("Check Answers", key="check_answers_2"): score = sum(1 for idx, q in enumerate(sentences) if user_answers[idx] == q["answer"]) total = len(sentences) st.markdown(f"### Your Score: {score}/{total}") if score == total: st.success("Great job! You answered all questions correctly.") st.session_state.tt_l2_t5_1_all_correct = True else: st.error("You made some mistakes. Please review the rule section and try again.") st.session_state.tt_l2_t5_1_error_count += 1 if st.session_state.get('tt_l2_t5_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T5_1, key='tt_l2_t5_1_practice') with col2: if st.button('Next', key='tt_l2_t5_1_next'): complete_task_and_record_data( page_prefix="tt_l2_t5_1", task_id="TT_L2_T5_1", next_page_func=navigate_to_task2_5 ) def TT_L2_T6_1(): st.title("Second Conditional Sentences") if "tt_l2_t6_1_start_time" not in st.session_state: st.session_state.tt_l2_t6_1_start_time = datetime.datetime.now() add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    The second conditional sentence is used for imagined situations, often in advice or opinion-giving. It talks about hypothetical scenarios and their possible outcomes.

    Form:

    • If + past simple + would + base verb.

    Comma Usage: In second conditional sentences, if the 'if' clause comes first, use a comma before the main clause. If the main clause comes first, no comma is needed.

    Example: "If I had more time, I would learn a new language."

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Advice

    If I were you, I would go to a small school in the countryside.

    Example 2: Hypothetical Situation

    Maybe it would be more fun if you went with your friends.

    Example 3: Hypothetical Movement

    But if I was able to move, I would live near the coast because I love the sea.
    """, unsafe_allow_html=True) # Practice Section: Drag and Drop Task st.markdown("""

    🎯 Practice: Match the Sentence Halves

    Drag and drop to match the first half of the sentence (main clause) with the correct second half (subordinate clause) for a second conditional sentence.

    """, unsafe_allow_html=True) # Drag-and-Drop Component drag_and_drop_css = """ """ drag_and_drop_js = """
    If I were you,
    Maybe it would be more fun
    But if I was able to move,
    I would go to a small school in the countryside.
    if you went with your friends.
    I would live near the coast because I love the sea.

    """ components.html(drag_and_drop_css + drag_and_drop_js, height=700) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T6_1, key='tt_l2_t6_1_practice') with col2: if st.button('Next', key='tt_l2_t6_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t6_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T6_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t6_1_start_time = None navigate_to_task2_6() st.rerun() def TT_L2_T7_1(): st.title("Past Perfect Tense") if "tt_l2_t7_1_start_time" not in st.session_state: st.session_state.tt_l2_t7_1_start_time = datetime.datetime.now() add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    The Past Perfect tense is used to talk about a time before another time in the past. It often comes in complex sentences where one action happened before another.

    Form:

    • Past Perfect: had + past participle

    Comma Usage: When the subordinate clause comes before the main clause, a comma is used to separate them. When the main clause comes first, no comma is needed.

    Example: "She had finished her homework before she went to bed."

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Sequence of Events

    After she had eaten dinner, she went for a walk.

    Example 2: Cause and Effect

    They missed the final decision, because they had left before the meeting ended.
    """, unsafe_allow_html=True) # Practice Section: Drag and Drop Task st.markdown("""

    🎯 Practice: Match the Sentence Halves

    Drag and drop to match the first half of the sentence (main clause) with the correct second half (subordinate clause) for a complex sentence with the Past Perfect tense.

    """, unsafe_allow_html=True) # Import Streamlit components import streamlit.components.v1 as components # Drag-and-Drop Component CSS drag_and_drop_css = """ """ # Drag-and-Drop Component JavaScript drag_and_drop_js = """
    I had arranged an appointment with my doctor
    before I called you.
    I felt really sorry for you
    after I had read your last letter.
    After she had eaten dinner,
    she went for a walk.
    They missed the final decision
    because they had left before the meeting ended.

    """ # Combine CSS and JavaScript html_code = drag_and_drop_css + drag_and_drop_js components.html(drag_and_drop_css + drag_and_drop_js, height=700) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T7_1, key='tt_l2_t7_1_practice') with col2: if st.button('Next', key='tt_l2_t7_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t7_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T7_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t7_1_start_time = None navigate_to_task2_7() st.rerun() def TT_L2_T8_1(): st.title("Reported Speech") if "tt_l2_t8_1_start_time" not in st.session_state: st.session_state.tt_l2_t8_1_start_time = datetime.datetime.now() add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Reported speech is used to tell someone what another person said. It involves changes in pronouns, tense, and sometimes word order.

    Reported Statements:

    • Use 'say' or 'tell' + 'that-' clause, adjusting pronoun and tense as necessary.
    • Direct Speech: "I am going to the market."
    • Reported Speech: "She said that she was going to the market."

    Reported 'Yes-No' Questions:

    • Use 'ask' + 'if' or 'whether' + clause.
    • Direct Speech: "Did you finish your homework?"
    • Reported Speech: "He asked if I had finished my homework."

    Other Types of Reported Questions:

    • For 'Wh-' questions, use 'ask' + wh-word + clause.
    • Direct Speech: "Where do you live?"
    • Reported Speech: "She asked where I lived."

    Remember to change the pronouns and shift the tense back when reporting speech. For example, "I am going" changes to "she was going."

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Reported Statement

    Direct Speech: "I am going to the market."

    She said that she was going to the market.

    Example 2: Reported 'Yes-No' Question

    Direct Speech: "Did you finish your homework?"

    He asked if I had finished my homework.

    Example 3: Reported 'Wh-' Question

    Direct Speech: "Where do you live?"

    She asked where I lived.
    """, unsafe_allow_html=True) # Practice Section: Matching Task st.markdown("""

    🎯 Practice: Match the Direct Speech to the Reported Speech

    Drag and drop to match the direct speech with the correct reported speech.

    """, unsafe_allow_html=True) # Drag-and-Drop Component drag_and_drop_css = """ """ drag_and_drop_js = """

    Drag and Drop Task: Match the Direct Speech with Reported Speech

    "I am going to the park," she said.
    "Did you finish your homework?" he asked.
    "Where do you live?" she asked.
    "I have completed the project," he said.
    "Are you attending the meeting?" she asked.
    She said that she was going to the park.
    He asked if I had finished my homework.
    She asked where I lived.
    He said that he had completed the project.
    She asked if I was attending the meeting.

    """ components.html(drag_and_drop_css + drag_and_drop_js, height=900) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T8_1, key='tt_l2_t8_1_practice') with col2: if st.button('Next', key='tt_l2_t8_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t8_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T8_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t8_1_start_time = None navigate_to_tt_l2_t8_2() st.rerun() def TT_L2_T8_2(): st.title("Reported Speech") add_css() if 'tt_l2_t8_2_all_correct' not in st.session_state: st.session_state.tt_l2_t8_2_all_correct = False if "tt_l2_t8_2_start_time" not in st.session_state: st.session_state.tt_l2_t8_2_start_time = datetime.datetime.now() if "tt_l2_t8_2_error_count" not in st.session_state: st.session_state.tt_l2_t8_2_error_count = 0 # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Reported speech is used to convey what someone else has said. It involves changes in pronouns, tenses, and sometimes time/place expressions. Here are the key rules for reported speech:

    Tense Changes in Reported Speech:

    Direct Speech (Tense) Reported Speech (Tense)
    Present Simple Past Simple
    Present Continuous Past Continuous
    Present Perfect Past Perfect
    Past Simple Past Perfect
    Will Would

    Key Word Changes in Reported Speech:

    Direct Speech (Key Word) Reported Speech (Key Word)
    Today That day
    Now Then
    Here There
    Yesterday The day before
    Tomorrow The next day

    Reported Commands and Requests:

    • Use 'ask' or 'tell' + direct object + 'to-' infinitive.
    • Example: "She told me to close the door."
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Reported Statement

    She said that she was going to the market.

    Example 2: Reported 'Yes-No' Question

    He asked if I had finished my homework.

    Example 3: Reported Request

    She told me to close the door.

    Example 4: Reported 'Wh-' Question

    He asked where I was going.
    """, unsafe_allow_html=True) # Practice Section: Sentence Transformation st.markdown("""

    🎯 Practice: Transform Direct Speech into Reported Speech

    Below are sentences in direct speech. Transform them into reported speech by selecting the correct options from the dropdowns.

    """, unsafe_allow_html=True) # Practice sentences direct_sentences = [ ("1. She said, 'I am coming.'", "She said that she was coming."), ("2. He asked, 'Are you hungry?'", "He asked if I was hungry."), ("3. They told me, 'Close the door.'", "They told me to close the door."), ("4. She asked, 'Where are you going?'", "She asked where I was going."), ] # Variables to store correct answers correct_answers = [ {"subject": "She", "verb": "said", "conjunction": "that", "object": "", "verb_form": "she was coming"}, {"subject": "He", "verb": "asked", "conjunction": "if", "object": "I", "verb_form": "was hungry"}, {"subject": "They", "verb": "told", "conjunction": "", "object": "me", "verb_form": "to close the door"}, {"subject": "She", "verb": "asked", "conjunction": "where", "object": "I", "verb_form": "was going"}, ] total = len(direct_sentences) for i, (direct, reported) in enumerate(direct_sentences): st.write(f"**Direct Speech {i+1}:** {direct}") col1, col2, col3, col4, col5 = st.columns(5) with col1: subject = st.selectbox('Subject', ['She', 'He', 'They'], key=f'subject_{i}') with col2: verb = st.selectbox('Reporting Verb', ['said', 'asked', 'told'], key=f'verb_{i}') with col3: conjunction_options = ['that', 'if', 'to', 'where', ''] conjunction = st.selectbox('Conjunction', conjunction_options, key=f'conjunction_{i}') with col4: object_options = ['', 'me', 'I', 'she', 'he', 'they'] object_ = st.selectbox('Object', object_options, key=f'object_{i}') with col5: verb_form_options = ['she was coming', 'was hungry', 'to close the door', 'was going'] verb_form = st.selectbox('Verb Form', verb_form_options, key=f'verb_form_{i}') # Build the user's sentence user_sentence = f"{subject} {verb} " if conjunction: user_sentence += f"{conjunction} " if object_: user_sentence += f"{object_} " user_sentence += f"{verb_form}." st.markdown(f"
    {user_sentence}
    ", unsafe_allow_html=True) # Feedback Button if st.button("Check Answers"): score = 0 for i in range(total): user_subject = st.session_state.get(f'subject_{i}', '') user_verb = st.session_state.get(f'verb_{i}', '') user_conjunction = st.session_state.get(f'conjunction_{i}', '') user_object = st.session_state.get(f'object_{i}', '').strip() user_verb_form = st.session_state.get(f'verb_form_{i}', '') # Build the user answer components user_components = { "subject": user_subject, "verb": user_verb, "conjunction": user_conjunction, "object": user_object, "verb_form": user_verb_form, } # Compare with correct answers if user_components == correct_answers[i]: score +=1 st.markdown(f"### Your Score: {score}/{total}") if score == total: st.success("Great job! You answered all questions correctly.") st.session_state.tt_l2_t8_2_all_correct = True else: st.error(f"You got {score} out of {total} correct. Try again.") # FIXED LINE st.session_state.tt_l2_t8_2_all_correct = False if st.session_state.get('tt_l2_t8_2_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L2_T8_2, key='tt_l2_t8_2_practice') with col2: if st.button('Next', key='tt_l2_t8_2_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l2_t8_2_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L2_T8_2" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l2_t8_2_start_time = None navigate_to_task2_8() if st.button('Back', key='tt_l2_t8_2_back'): back_to_tt_l2_t8_1() st.rerun() def TT_L3_T1_1(): st.title("Introduction to Sentence Structure") if "tt_l3_t1_1_start_time" not in st.session_state: st.session_state.tt_l3_t1_1_start_time = datetime.datetime.now() add_css() # Understanding Parts of Speech Section with Yellow Frame st.markdown("""

    πŸ“š Understanding Parts of Speech

    Parts of speech are the fundamental building blocks of sentences. They describe the role each word plays within a sentence.

    Use the dropdown lists below to explore different parts of speech and see how they fit together to form meaningful sentences.

    """, unsafe_allow_html=True) # Dropdowns for Parts of Speech with Color Coding col1, col2, col3, col4, col5 = st.columns(5) with col1: selected_determiner = st.selectbox('Determiner', ['a', 'the', 'some', 'any'], key='selectbox1') with col2: selected_noun = st.selectbox('Noun', ['car', 'dog', 'house', 'book'], key='selectbox2') with col3: selected_verb = st.selectbox('Verb', ['run', 'jump', 'swim', 'read'], key='selectbox3') with col4: selected_adjective = st.selectbox('Adjective', ['red', 'big', 'quick', 'blue'], key='selectbox4') with col5: selected_adverb = st.selectbox('Adverb', ['quickly', 'silently', 'well', 'badly'], key='selectbox5') set_selectbox_style(0, 'darkblue', '10px') # Determiner set_selectbox_style(1, 'rgba(255, 255, 0, 0.5)', '10px') # Noun set_selectbox_style(2, 'rgba(255, 0, 0, 0.5)', '10px') # Verb set_selectbox_style(3, 'lightgreen', '10px') # Adjective set_selectbox_style(4, 'lightblue', '10px') # Adverb # Merged Understanding Phrases and Phrase Level Section with Blue Frame st.markdown("""

    πŸ“– Understanding Phrases

    Phrases are groups of words that act together as a single part of speech but do not contain both a subject and a verb.

    Here's how different phrases are color-coded for easy identification:

    • Noun Phrase: Functions as a noun. Example: a red house.
    • Verb Phrase: Consists of a main verb and its auxiliaries. Example: is running.
    • Prepositional Phrase: Begins with a preposition and ends with a noun or pronoun. Example: in the park.

    Noun Phrases

    a red house
    the book

    Verb Phrases

    is running
    has been reading
    """, unsafe_allow_html=True) # Merged Clause Level and Examples Sections with Yellow Frame st.markdown("""

    πŸ“œ Understanding Clauses

    Clauses are groups of words that contain a subject and a predicate. They can be independent (main clauses) or dependent (subordinate clauses).

    Main Clause: Can stand alone as a complete sentence.

    Subordinate Clause: Cannot stand alone and depends on the main clause.

    In clauses, we can identify:

    • Subject: The person or thing performing the action ((black text, underlined).
    • Verb: The action or state of being (blue text, underlined).
    • Object: Receives the action of the verb (green text, underlined).

    Examples:

    She runs every day.
    because she wants to stay fit.
    He finished his homework.
    after he came home.
    """, unsafe_allow_html=True) # Practice Section: Drag and Drop Task st.markdown("""

    🎯 Practice: Construct Complex Sentences

    Drag and drop the phrases to construct meaningful and grammatically correct sentences. This exercise will help you understand how main and subordinate clauses work together in a sentence.

    """, unsafe_allow_html=True) # Drag-and-Drop Component CSS drag_and_drop_css = """ """ # Drag-and-Drop Component JavaScript drag_and_drop_js = """
    I had arranged an appointment with my doctor
    before I called you.
    I felt really sorry for you
    after I had read your last letter.
    They missed the final decision
    because they had left before the meeting ended.

    """ # Combine CSS and JavaScript for the Drag-and-Drop Component html_code = drag_and_drop_css + drag_and_drop_js # Display the drag-and-drop component components.html(html_code, height=800, scrolling=True) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T1_2, key='tt_l3_t1_1_practice') with col2: if st.button('Next', key='tt_l3_t1_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l3_t1_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L3_T1_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l3_t1_1_start_time = None navigate_to_tt_l3_t1_2() st.rerun() def TT_L3_T1_2(): st.title("Third Conditional Sentences") if 'tt_l3_t1_2_all_correct' not in st.session_state: st.session_state.tt_l3_t1_2_all_correct = False if "tt_l3_t1_2_start_time" not in st.session_state: st.session_state.tt_l3_t1_2_start_time = datetime.datetime.now() if "tt_l3_t1_2_error_count" not in st.session_state: st.session_state.tt_l3_t1_2_error_count = 0 add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    The Third Conditional is used to talk about imagined situations in the past, often involving regret. The structure is:

    • Form: 'if' + past perfect simple, and 'would have' + past participle in the main clause.
    • Example: If we had left earlier, we would have caught the train.
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Third Conditional

    If she had studied more, she would have passed the exam.

    Example 2: Third Conditional

    If they had booked tickets earlier, they would have gone to the concert.
    """, unsafe_allow_html=True) # Practice Section for Third Conditional st.markdown("""

    🎯 Practice: Third Conditional

    Complete the sentences by selecting the correct options.

    """, unsafe_allow_html=True) # Define sentences with blanks to fill in sentences = [ { "parts": [ "1. If she ", {"key": "s1_v1", "options": ["had studied", "studied", "would study"]}, " harder, she ", {"key": "s1_v2", "options": ["would have passed", "would pass", "had passed"]}, " the exam." ], "answers": { "s1_v1": "had studied", "s1_v2": "would have passed" } }, { "parts": [ "2. If they ", {"key": "s2_v1", "options": ["had left", "left", "would leave"]}, " earlier, they ", {"key": "s2_v2", "options": ["would have caught", "would catch", "had caught"]}, " the train." ], "answers": { "s2_v1": "had left", "s2_v2": "would have caught" } }, { "parts": [ "3. If we ", {"key": "s3_v1", "options": ["had known", "knew", "would know"]}, " about the party, we ", {"key": "s3_v2", "options": ["would have gone", "would go", "had gone"]}, "." ], "answers": { "s3_v1": "had known", "s3_v2": "would have gone" } }, { "parts": [ "4. If I ", {"key": "s4_v1", "options": ["had seen", "saw", "would see"]}, " him, I ", {"key": "s4_v2", "options": ["would have told", "would tell", "had told"]}, " him the news." ], "answers": { "s4_v1": "had seen", "s4_v2": "would have told" } }, { "parts": [ "5. If you ", {"key": "s5_v1", "options": ["had called", "called", "would call"]}, " me, I ", {"key": "s5_v2", "options": ["would have picked", "would pick", "had picked"]}, " you up." ], "answers": { "s5_v1": "had called", "s5_v2": "would have picked" } } ] user_answers = {} # Render sentences with proper alignment for idx, sentence in enumerate(sentences): sentence_row = st.container() with sentence_row: # Use columns to align each part of the sentence in the same row cols = st.columns(len(sentence["parts"])) for i, part in enumerate(sentence["parts"]): if isinstance(part, dict): # Dropdowns key = part["key"] options = part["options"] user_answers[key] = cols[i].selectbox("", options, key=key, label_visibility="collapsed") else: # Static text cols[i].markdown(f"

    {part}

    ", unsafe_allow_html=True) # Add spacing below the last sentence st.markdown("
    ", unsafe_allow_html=True) # Check Answers Button if st.button("Check Answers"): score = 0 total = len(sentences) for sentence in sentences: correct = True for key, correct_answer in sentence["answers"].items(): if user_answers.get(key) != correct_answer: correct = False if correct: score += 1 st.markdown(f"### Your Score: {score}/{total}") if score == total: st.success("Great job! You answered all questions correctly.") st.session_state.tt_l3_t1_2_all_correct = True # Correctly update session state else: st.error(f"You got {score} out of {len(score)} correct. Try again.") st.session_state.tt_l3_t1_2_error_count += 1 # Ensure flag is False if st.session_state.get('tt_l3_t1_2_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T1_2, key='tt_l3_t1_2_practice') with col2: if st.button('Next', key='tt_l3_t1_2_next'): complete_task_and_record_data( page_prefix="tt_l3_t1_2", task_id="TT_L3_T1_2", next_page_func=navigate_to_tt_l3_t1_3 ) if st.button('Back', key='tt_l3_t1_2_back'): back_to_tt_l3_t1_1() st.rerun() def TT_L3_T1_3(): st.title("Third Conditional Sentences") if "tt_l3_t1_3_start_time" not in st.session_state: st.session_state.tt_l3_t1_3_start_time = datetime.datetime.now() add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Zero Conditional: The Zero Conditional is used for things that are always true or very likely to happen. It refers to general truths or facts. It's formed using the Present Simple tense in both the 'if' clause and the main clause.

    Example: If you boil water, it turns into steam.

    First Conditional: The First Conditional is used to talk about likely or possible results in the future. It's formed using the Present Simple tense in the 'if' clause and 'will' or a modal verb in the main clause.

    Example: If it rains tomorrow, we will cancel the picnic.

    Second Conditional: The Second Conditional is used to talk about unreal or unlikely situations in the present or future. It's formed using the Past Simple tense in the 'if' clause and 'would' + base form of the verb in the main clause.

    Example: If I won the lottery, I would travel around the world.

    Third Conditional: The Third Conditional is used to talk about imagined situations in the past, often involving regret. It's formed using 'if' + Past Perfect tense in the 'if' clause and 'would have' + past participle in the main clause.

    Example: If we had left earlier, we would have caught the train.

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Zero Conditional

    If you heat ice , it melts.

    Example 2: First Conditional

    If you study , you will pass the exam.

    Example 3: Second Conditional

    If I won the lottery , I would travel around the world.

    Example 4: Third Conditional

    If she had studied more , she would have passed the exam.
    """, unsafe_allow_html=True) # Practice Section: Conditional Matching st.markdown("""

    🎯 Practice: Match the Sentence Halves

    Drag and drop to match the first half of the sentence with the correct second half.

    """, unsafe_allow_html=True) drag_and_drop_css = """ """ drag_and_drop_js = """
    If I have spare time
    I always read a book.
    If you tell them your reasons this time
    your parents will accept it.
    You can get to my house
    if you take the number 35 bus.
    If you don’t do some sports activities
    you will gain a lot of weight.
    If I hadn't gone to these horse-riding lessons
    I wouldn't have lost my watch.
    People in Bangkok would not have to face so much stress in the streets
    if there were better transportation system.

    """ # Combine CSS and JavaScript for the Drag-and-Drop Component html_code = drag_and_drop_css + drag_and_drop_js # Display the drag-and-drop component components.html(html_code, height=1000, scrolling=True) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T1_3, key='tt_l3_t1_3_practice') with col2: if st.button('Next', key='tt_l3_t1_3_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l3_t1_3_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L3_T1_3" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l3_t1_3_start_time = None navigate_to_task3_1() st.rerun() if st.button('Back', key='tt_l3_t1_3_back'): back_to_tt_l3_t1_2() st.rerun() def TT_L3_T2_1(): st.title("Subordinating Conjunctions") if 'tt_l3_t2_1_all_correct' not in st.session_state: st.session_state.tt_l3_t2_1_all_correct = False if "tt_l3_t2_1_start_time" not in st.session_state: st.session_state.tt_l3_t2_1_start_time = datetime.datetime.now() if "tt_l3_t2_1_error_count" not in st.session_state: st.session_state.tt_l3_t2_1_error_count = 0 add_css() st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Subordinating conjunctions are used to introduce subordinate clauses, which provide additional information to the main clause. Understanding how to use these conjunctions effectively allows you to construct more complex and meaningful sentences.

    Types of Subordinating Conjunctions:

    • Simple Subordinating Conjunctions: Includes conjunctions like 'as', 'after', 'before', 'since', 'until', 'although', 'whether', 'so (that)', and 'though'.
    • Complex Subordinating Conjunctions: These are phrases like 'as long as', 'as soon as', 'in order that', 'despite the fact that', 'due to the fact that', 'as if', and 'as though'.

    Let's explore how these conjunctions are used in sentences:

    Using Commas with Subordinating Conjunctions:

    When the subordinate clause comes before the main clause, a comma is usually used to separate the two clauses. For example:

    • Although she was tired, she continued working.
    • If you study hard, you will succeed.

    However, when the subordinate clause follows the main clause, no comma is usually needed:

    • She continued working although she was tired.
    • You will succeed if you study hard.
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Simple Subordinating Conjunction

    She went home after she finished work.

    Example 2: Complex Subordinating Conjunction

    They started the meeting as soon as everyone arrived.

    Example 3: Subordinating Conjunction for Contrast

    Even though he was tired, he continued working.
    """, unsafe_allow_html=True) # Practice Section: Select the Correct Conjunction st.markdown("""

    🎯 Practice: Select the Correct Conjunction

    Select the appropriate subordinating conjunction to complete each sentence correctly.

    """, unsafe_allow_html=True) # Define the sentences with blanks and options sentences = [ { "text": "1. ________ the bus arrived, everyone rushed to get on it because the rain was pouring heavily.", "options": ["Until", "After", "Before", "As soon as"], "answer": "As soon as", "key": "q1" }, { "text": "2. People do different leisure activities ________ they have different interests.", "options": ["which", "when", "before", "since"], "answer": "since", "key": "q2" }, { "text": "3. ________ many celebrities get bad comments on social media, they are still famous among their fans.", "options": ["Due to", "In case of", "In spite of", "Even though"], "answer": "Even though", "key": "q3" }, { "text": "4. Nowadays, many new kinds of illnesses make people sick easily ________ they are healthy.", "options": ["due to the fact that", "despite the fact that", "because of", "in case of"], "answer": "despite the fact that", "key": "q4" }, { "text": "5. She decided to stay home ________ it was raining heavily outside.", "options": ["because", "if", "unless", "although"], "answer": "because", "key": "q5" }, ] user_answers = [] # Loop through sentences and use radio buttons for options for sentence in sentences: st.write(sentence["text"]) user_answer = st.radio("", sentence["options"],index=None, key=sentence["key"]) user_answers.append((user_answer, sentence["answer"])) # Check Answers button if st.button("Check Answers"): score = sum(1 for user_answer, correct_answer in user_answers if user_answer == correct_answer) total = len(sentences) st.markdown(f"### Your Score: {score}/{total}") if score == total: st.success("Great job! You answered all questions correctly.") st.session_state.tt_l3_t2_1_all_correct = True else: st.error("You made some mistakes. Please review the rule section and try again.") st.session_state.tt_l3_t2_1_error_count += 1 if st.session_state.get('tt_l3_t2_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T2_1, key='tt_l3_t2_1_practice') with col2: if st.button('Next', key='tt_l3_t2_1_next'): complete_task_and_record_data( page_prefix="tt_l3_t2_1", task_id="TT_L3_T2_1", next_page_func=navigate_to_task3_2 ) def TT_L3_T3_1(): st.title("TT_L3_T3_1: Present Perfect Continuous") if 'tt_l3_t3_1_all_correct' not in st.session_state: st.session_state.tt_l3_t3_1_all_correct = False if "tt_l3_t3_1_start_time" not in st.session_state: st.session_state.tt_l3_t3_1_start_time = datetime.datetime.now() if "tt_l3_t3_1_error_count" not in st.session_state: st.session_state.tt_l3_t3_1_error_count = 0 add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    The Present Perfect Continuous tense is used to describe an action that started in the past and continues up to the present, or was recently completed but has a present effect. It emphasizes the duration of the activity.

    Key Words: since, for, lately, recently, all day, all morning.

    Form:

    • Affirmative: Subject + have/has + been + verb + -ing
    • Negative: Subject + have/has + not + been + verb + -ing
    • Question: Have/Has + subject + been + verb + -ing?

    Difference between Present Perfect and Present Perfect Continuous:

    The Present Perfect tense is used to express completed actions at an unspecified time before now, focusing on the result.

    The Present Perfect Continuous tense emphasizes the duration or ongoing nature of an action that started in the past and continues in the present.

    Example:

    • Present Perfect: She has written three letters.
    • Present Perfect Continuous: She has been writing letters since this morning.
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Affirmative Sentences

    I have been working on this project for two hours.

    They have been traveling around the world since last year.

    Negative Sentences

    He has not been feeling well lately.

    Questions

    Have you been studying English for a long time?

    """, unsafe_allow_html=True) # Practice Section: Multiple Choice Questions st.markdown("""

    🎯 Practice: Choose the Correct Verb Form

    Select the correct verb form to complete each sentence.

    """, unsafe_allow_html=True) # List of questions and options questions = [ { 'question': '1. Although they ______ a photocopying machine for quite some time, they still cannot find what caused a paper jam.', 'options': ['fixed', 'being fixed', 'have been fixing', 'fix'], 'answer': 'have been fixing' }, { 'question': '2. My team ________ the inventory since our boss left, yet it is not properly done.', 'options': ['will be checking', 'checks', 'has been checking', 'is checking'], 'answer': 'has been checking' }, { 'question': '3. Jane ______ her piano skills all day for a few months to get herself ready for an upcoming piano contest.', 'options': ['does practice', 'practices', 'is practicing', 'has been practicing'], 'answer': 'has been practicing' }, { 'question': '4. It ________ so heavily and continuously for three days, now it finally stopped.', 'options': ['has been snowing', 'is snowy', 'has snowed', 'snowed'], 'answer': 'has been snowing' }, { 'question': '5. My dad along with my older brother ______ the car since 8 o’clock. Now they look so filthy.', 'options': ['repair', 'has been repairing', 'are repairing', 'have been repairing'], 'answer': 'have been repairing' } ] user_answers = [] for idx, q in enumerate(questions): st.write(q['question']) options = q['options'] user_answer = st.radio(f"",options, index=None, key=f"q{idx}") user_answers.append(user_answer) if st.button("Check Answers"): total = len(questions) score = sum(1 for idx, q in enumerate(questions) if user_answers[idx] == q['answer']) st.write(f"Your score: **{score} out of {total}**") if score == total: st.success("Great job! You answered all questions correctly.") st.session_state.tt_l3_t3_1_all_correct = True else: st.error("You made some mistakes. Please review the rule section and try again.") st.session_state.tt_l3_t3_1_error_count += 1 if st.session_state.get('tt_l3_t3_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T3_1, key='tt_l3_t3_1_practice') with col2: if st.button('Next', key='tt_l3_t3_1_next'): complete_task_and_record_data( page_prefix="tt_l3_t3_1", task_id="TT_L3_T3_1", next_page_func=navigate_to_task3_4 ) def TT_L3_T4_1(): st.title("TT_L3_T4_1: Gerunds") add_css() if 'tt_l3_t4_1_all_correct' not in st.session_state: st.session_state.tt_l3_t4_1_all_correct = False if "tt_l3_t4_1_start_time" not in st.session_state: st.session_state.tt_l3_t4_1_start_time = datetime.datetime.now() if "tt_l3_t4_1_error_count" not in st.session_state: st.session_state.tt_l3_t4_1_error_count = 0 # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Gerunds are verbs that end in '-ing' and function as nouns in a sentence. They can act as subjects, objects, or complements.

    Introduction of a new subject before the '-ing' form: This often involves a possessive adjective or an object pronoun before the gerund.

    Examples:

    • "John enjoys his mother baking cookies."
    • "I enjoy reading books."

    Verbs connected with the senses + direct object + '-ing' form: Verbs like 'see,' 'hear,' 'feel,' etc., are followed by a direct object and a gerund to emphasize an ongoing action.

    Example:

    • "She heard them singing a song."

    Some verbs are naturally followed by the gerund form.

    Examples:

    • "They avoid driving at night."
    • "We enjoy playing soccer."
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Gerunds with New Subjects

    "My friend appreciates my helping him with homework."

    Example 2: Verbs of Perception with Gerunds

    "We saw the dog running in the park."

    Example 3: Verbs Followed by Gerunds

    "They enjoy hiking mountains."
    """, unsafe_allow_html=True) # Practice Section: Identifying Correct Use of Gerunds st.markdown("""

    🎯 Practice: Choose the Correct Form

    Select the correct form (gerund or infinitive) to complete each sentence.

    """, unsafe_allow_html=True) # Practice Questions questions = [ { 'question': '1. She enjoys _____ to music.', 'options': ['listening', 'to listen'], 'answer': 'listening' }, { 'question': '2. They decided _____ a new car.', 'options': ['buying', 'to buy'], 'answer': 'to buy' }, { 'question': '3. He avoids _____ in crowded places.', 'options': ['being', 'to be'], 'answer': 'being' }, { 'question': '4. We plan _____ a trip next month.', 'options': ['taking', 'to take'], 'answer': 'to take' }, { 'question': '5. I can\'t help _____ excited about the concert.', 'options': ['feeling', 'to feel'], 'answer': 'feeling' } ] user_answers = [] for idx, q in enumerate(questions): st.write(q['question']) options = q['options'] user_answer = st.radio( f"",options, index=None, key=f"q{idx}") user_answers.append(user_answer) if st.button("Check Answers"): score = 0 total = len(questions) for idx, q in enumerate(questions): if user_answers[idx] == q['answer']: score += 1 st.write(f"Your score: **{score} out of {total}**") if score == total: st.success("Great job! You answered all questions correctly.") st.session_state.tt_l3_t4_1_all_correct = True else: st.error("You made some mistakes. Please review the rule section and try again.") st.session_state.tt_l3_t4_1_error_count += 1 if st.session_state.get('tt_l3_t4_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T4_1, key='tt_l3_t4_1_practice') with col2: if st.button('Next', key='tt_l3_t4_1_next'): complete_task_and_record_data( page_prefix="tt_l3_t4_1", task_id="TT_L3_T4_1", next_page_func=navigate_to_tt_l3_t4_2 ) def TT_L3_T4_2(): st.title("Gerunds and Infinitives") if "tt_l3_t4_2_start_time" not in st.session_state: st.session_state.tt_l3_t4_2_start_time = datetime.datetime.now() add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Gerunds are verbs ending in '-ing' that function as nouns in a sentence. They can act as subjects, objects, or complements.

    Infinitives are the base form of a verb, often preceded by 'to' (to eat, to run). They can also function as nouns, adjectives, or adverbs.

    Some verbs can be followed by either a gerund or an infinitive, but the meaning may change. Other verbs are only followed by one or the other.

    Key Points:

    • After certain verbs, use a gerund. Example: "She enjoys swimming."
    • After certain verbs, use an infinitive. Example: "They decided to leave early."
    • After some verbs, both forms are possible but with a change in meaning. Example: "He stopped smoking." vs. "He stopped to smoke."
    • Gerunds can follow prepositions. Example: "She is good at painting."
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1:

    If your boss doesn't mind, you joining us could be fun.

    Example 2:

    I can't stand people talking loudly in the library.

    Example 3:

    We saw someone dancing in the street.
    """, unsafe_allow_html=True) # Practice Section: Complex Sentences Using Gerunds (Drag-and-Drop Task) st.markdown("""

    🎯 Practice: Form Sentences Using Gerunds

    Drag and drop the phrases into the correct order to form meaningful sentences. Each drop zone corresponds to one sentence.

    """, unsafe_allow_html=True) # CSS for Drag-and-Drop Component drag_and_drop_css = """ """ drag_and_drop_js = """
    If your boss doesn't mind
    you delaying
    it could be a possible solution.
    I could not stand people
    shouting at me or
    taking pictures of me all day.
    Then I heard
    someone screaming and
    I told the others to go.
    We cannot see animals
    running, eating, or
    hunting out of their habitat.

    """ # Combine CSS and JS for the drag-and-drop component components.html(drag_and_drop_css + drag_and_drop_js, height=900) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T4_2, key='tt_l3_t4_2_practice') with col2: if st.button('Next', key='tt_l3_t4_2_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l3_t4_2_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L3_T4_2" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l3_t4_2_start_time = None navigate_to_task3_4() st.rerun() if st.button('Back', on_click=back_to_tt_l3_t4_1, key='tt_l3_t4_2_back'): back_to_tt_l3_t4_1() st.rerun() def TT_L3_T5_1(): st.title("TT_L3_T5_1: Relative Clauses") add_css() if 'tt_l3_t5_1_all_correct' not in st.session_state: st.session_state.tt_l3_t5_1_all_correct = False if "tt_l3_t5_1_start_time" not in st.session_state: st.session_state.tt_l3_t5_1_start_time = datetime.datetime.now() if "tt_l3_t5_1_error_count" not in st.session_state: st.session_state.tt_l3_t5_1_error_count = 0 # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Relative Clauses: Relative clauses are used to give additional information about a noun. They start with a relative pronoun and function like adjectives, providing more details about the noun.

    Relative Pronouns:

    • Who: Refers to people. Example: "The teacher who taught me."
    • Whom: Formal object form referring to people. Example: "The person whom you met."
    • Whose: Shows possession. Example: "The student whose laptop was stolen."
    • Which: Refers to things. Example: "The book which I read."
    • That: Refers to people or things in defining clauses. Example: "The car that he bought."
    • Where: Refers to places. Example: "The city where I was born."
    • When: Refers to times. Example: "The day when we met."
    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Example 1: Relative Clause with "Whom"

    The professor whom we met yesterday is giving a lecture tonight.

    Example 2: Relative Clause with "Whose"

    The artist whose paintings you admire is having an exhibition.

    Example 3: Relative Clause with "Where"

    The cafΓ© where we had coffee has closed down.

    Example 4: Relative Clause with "When"

    The year when they got married was 1990.

    Example 5: Relative Clause with "Who"

    Students who study hard achieve good grades.
    """, unsafe_allow_html=True) # Practice Section: Multiple Choice Questions st.markdown("""

    🎯 Practice: Choose the Correct Relative Pronoun

    Select the correct relative pronoun to complete each sentence.

    """, unsafe_allow_html=True) # List of questions and options questions = [ { 'question': '1. The old woman was a pleasant person __________ everyone in the neighborhood respected and loved.', 'options': ['when', 'where', 'whom', 'whose'], 'answer': 'whom' }, { 'question': '2. Participating in English activities or practicing English at home are particularly good for the students for __________ English is a foreign language.', 'options': ['whom', 'when', 'whose', 'where'], 'answer': 'whom' }, { 'question': '3. The Thai chef __________ signature dish won the top prize on a cooking show is opening a restaurant in Japan.', 'options': ['which', 'whose', 'whom', 'who'], 'answer': 'whose' }, { 'question': '4. The young woman smiled as she remembered the friendly stranger with __________ she had shared a conversation about their favorite books.', 'options': ['which', 'whose', 'who', 'whom'], 'answer': 'whom' }, { 'question': '5. The family doctor, __________ daughter is a pediatrician, is opening a free clinic for low-income patients.', 'options': ['whose', 'which', 'whom', 'where'], 'answer': 'whose' } ] user_answers = [] for idx, q in enumerate(questions): st.write(q['question']) options = q['options'] user_answer = st.radio(f"", options, index=None, key=f"q{idx}") user_answers.append(user_answer) if st.button("Check Answers"): score = 0 total = len(questions) # No need for all_correct variable since we can compare score with total for idx, q in enumerate(questions): if user_answers[idx] == q['answer']: score += 1 # No need for else block here since we're only incrementing score when correct st.write(f"Your score: **{score} out of {total}**") if score == total: st.success("Great job! You formed all sentences correctly.") st.session_state.tt_l3_t5_1_all_correct = True else: st.error("Some sentences are incorrect. Please try again.") st.session_state.tt_l3_t5_1_error_count += 1 if st.session_state.get('tt_l3_t5_1_all_correct'): col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T5_1, key='tt_l3_t5_1_practice') with col2: if st.button('Next', key='tt_l3_t5_1_next'): complete_task_and_record_data( page_prefix="tt_l3_t5_1", task_id="TT_L3_T5_1", next_page_func=navigate_to_task3_5 ) def TT_L3_T6_1(): st.title("Future Tenses") if "tt_l13_t6_1_start_time" not in st.session_state: st.session_state.tt_l3_t6_1_start_time = datetime.datetime.now() add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Future Simple: The Future Simple is used to talk about actions that will happen at a specific time in the future. It is formed using 'will' + the base form of the verb.

    Example: She will travel to New York next week.


    Future Continuous: The Future Continuous is used to talk about actions that will be in progress at a certain time in the future. It is formed using 'will be' + the '-ing' form of the verb.

    Example: She will be traveling to New York at this time tomorrow.


    Future Perfect: The Future Perfect is used to talk about actions that will be completed by a certain point in the future. It is formed using 'will have' + the past participle of the verb.

    Example: She will have traveled to New York by next week.

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Future Simple

    They will start the project tomorrow.

    Future Continuous

    They will be working on the project at this time tomorrow.

    Future Perfect

    They will have completed the project by next week.

    """, unsafe_allow_html=True) # Practice Section: Complete the Sentences st.markdown("""

    🎯 Practice: Complete the Sentences

    Drag and drop the correct verb phrases into the blanks to complete the sentences.

    """, unsafe_allow_html=True) # CSS for the Drag-and-Drop Component drag_and_drop_css = """ """ # JavaScript for Drag-and-Drop Logic with Improved Checking drag_and_drop_js = """
    will have shipped
    will be discussing
    will have finished
    will be giving
    will be sitting
    1. At this time next Tuesday, the students
    in the test room to take the mid-term exam.
    2. The marketing team
    the new product campaign at 2 pm tomorrow.
    3. The mayor
    the speech for another half hour.
    4. By the time I submit my thesis, I
    writing my research findings.
    5. Two weeks before Christmas,we
    all products to our customers.

    """ # Display the drag-and-drop component component_value = components.html(drag_and_drop_css + drag_and_drop_js, height=700, scrolling=True) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T6_1, key='tt_l3_t6_1_practice') with col2: if st.button('Next', key='tt_l3_t6_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l3_t6_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L3_T6_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l3_t6_1_start_time = None navigate_to_task3_6() st.rerun() def TT_L3_T7_1(): st.title("Making Deductions about the Past and Present with Modal Verbs") if "tt_l3_t7_1_start_time" not in st.session_state: st.session_state.tt_l3_t7_1_start_time = datetime.datetime.now() add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Making Deductions about the Past: We use modal verbs like 'must have', 'might have', 'could have', 'can't have' followed by a past participle to express certainty or possibility about past events.

    Example:

    She must have missed the train.


    Making Deductions about the Present: We use modal verbs like 'must', 'might', 'could', 'can't' followed by the base form or be + -ing form to express certainty or possibility about present situations.

    Example:

    He could be working late tonight.

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    Making Deductions about the Past

    They might have forgotten the meeting.

    Making Deductions about the Present

    She must be sleeping now.

    """, unsafe_allow_html=True) # Practice Section: Complete the Sentences st.markdown("""

    🎯 Practice: Complete the Sentences

    Drag and drop the correct phrases into the sentences to complete them.

    """, unsafe_allow_html=True) # CSS for drag-and-drop functionality drag_and_drop_css = """ """ # JavaScript for drag-and-drop logic drag_and_drop_js = """
    must have been
    might have seen
    could have won
    must not have been
    must be
    can win
    could see
    1. The murderer
    the butler as he was the only one without an alibi.
    2. She looks familiar. I
    her somewhere before.
    3. If he had not been sick, he
    the race.
    4. There
    something wrong with the system yesterday.
    5. The exhibition
    very fascinating. I see a lot of good reviews.

    """ # Combine CSS and JavaScript for the Drag-and-Drop Component html_code = drag_and_drop_css + drag_and_drop_js # Display the drag-and-drop component components.html(html_code, height=800, scrolling=True) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T7_1, key='tt_l3_t7_1_practice') with col2: if st.button('Next', key='tt_l3_t7_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l3_t7_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L3_T7_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l3_t7_1_start_time = None navigate_to_task3_7() st.rerun() def TT_L3_T8_1(): st.title("Inversion") if 'tt_l3_t8_1_all_correct' not in st.session_state: st.session_state.tt_l3_t8_1_all_correct = False if "tt_l3_t8_1_start_time" not in st.session_state: st.session_state.tt_l3_t8_1_start_time = datetime.datetime.now() if "tt_l3_t8_1_error_count" not in st.session_state: st.session_state.tt_l3_t8_1_error_count = 0 add_css() # Rule Section st.markdown("""

    πŸ“’ Rule

    Expand to see the rule

    Inversion in English: Inversion is used for emphasis, style, or to make sentences more formal. In these structures, the usual order of the subject and the verb is reversed.

    1. Inversion with 'Never'

    Rule: 'Never' can be placed at the beginning of a sentence, followed by an inverted subject and verb to emphasize a statement.

    Example: Never have I seen such chaos.

    2. Inversion with 'No sooner ... than'

    Rule: 'No sooner' is used at the beginning of a sentence, followed by an inversion of the auxiliary verb and subject, and then 'than' to talk about something that happened immediately before something else.

    Example: No sooner had I finished my work than the bell rang.

    3. Inversion with 'Not only ... but also'

    Rule: 'Not only' is used at the beginning of a sentence followed by the inversion of the auxiliary verb and subject, and is followed by 'but also' to add additional information.

    Example: Not only did he win the race, but also he set a new record.

    """, unsafe_allow_html=True) # Examples Section st.markdown("""

    πŸ“ Examples

    Expand to see examples

    1. Inversion with 'Never'

    Never have I experienced such a situation.

    2. Inversion with 'No sooner ... than'

    No sooner had they left than the storm started.

    3. Inversion with 'Not only ... but also'

    Not only did she finish the project, but also she presented it perfectly.

    """, unsafe_allow_html=True) # Practice Section: Drag-and-Drop Sentences st.markdown("""

    🎯 Practice: Form Inverted Sentences

    Drag and drop the phrases into the correct order to form meaningful sentences with inversion.

    """, unsafe_allow_html=True) drag_and_drop_css = """ """ drag_and_drop_js = """
    Never have I seen
    such a thing
    in my life.
    No sooner had we arrived
    than it started raining
    heavily outside.
    Not only did she win
    but also set a new record
    for the marathon.

    """ # Inject into Streamlit components.html(drag_and_drop_css + drag_and_drop_js, height=800) col1, col2 = st.columns(2) with col1: st.button('Practice', on_click=navigate_to_practice_TT_L3_T8_1, key='tt_l3_t8_1_practice') with col2: if st.button('Next', key='tt_l3_t8_1_next'): end_time = datetime.datetime.now() start_time = st.session_state.tt_l3_t8_1_start_time time_diff = end_time - start_time # Convert timedelta to HH:MM:SS total_seconds = int(time_diff.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) time_spent_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" try: h, m, s = map(int, time_spent_str.split(':')) time_obj = datetime.time(h, m, s) except ValueError as e: st.error(f"Error parsing time: {e}") time_obj = None # fallback errors_info = {"error_count": 0} try: student_id = st.session_state.user_info.id task_id = "TT_L3_T8_1" record_task_done( student_id=student_id, task_id=task_id, date=end_time, # current datetime time_spent=time_obj, errors_dict=errors_info ) except Exception as e: st.error(f"Error recording task: {e}") return st.session_state.tt_l3_t8_1_start_time = None navigate_to_task3_8() st.rerun() def load_lottieurl(url: str): r = requests.get(url) if r.status_code != 200: return None return r.json() def success_page(): # Do NOT call st.set_page_config here st.markdown("

    πŸŽ‰ Congratulations!

    ", unsafe_allow_html=True) # Load Lottie Animation lottie_animation = load_lottieurl("https://assets10.lottiefiles.com/packages/lf20_jbrw3hcz.json") # Example animation # Display Animation and Message in Columns col1, col2 = st.columns([1, 2]) with col1: if lottie_animation: st_lottie(lottie_animation, height=300, key="success") with col2: st.markdown("""

    You've successfully completed the program. Your dedication and hard work have paid off!

    Here's what you achieved:

    • Mastered key grammar concepts.
    • Enhanced your writing fluency.
    • Improved overall language proficiency.

    Keep up the great work and continue striving for excellence!

    """, unsafe_allow_html=True) st.markdown("
    ", unsafe_allow_html=True) # Call-to-Action Buttons col3, col4 = st.columns(2) with col3: if st.button("🏠 Return to Home"): st.session_state.page = 'user_dashboard' # Adjust according to your navigation logic # Optional: Add a Footer st.markdown("""

    Thank you for using our application. We wish you continued success in your learning journey!

    """, unsafe_allow_html=True) def color_code_guide(): st.title("Color Code Guide") add_css() st.markdown(""" """ , unsafe_allow_html=True) st.markdown("""

    Word Level

    - Noun

    - Verb

    - Adjective

    - Adverb

    - Determiner

    - Conjunction

    Phrase Level

    - Noun Phrase

    - Verb Phrase

    - Prepositional Phrase

    Clause Level

    - Subject

    - Verb

    - Object

    - Main Clause

    - Subordinate Clause

    """, unsafe_allow_html=True) pages = { "login_registration": login_registration_page, "mode_page": mode_page, "personal_info": personal_info_page, "proficiency": proficiency_page, "self_assessment": self_assessment_page, "user_dashboard": user_dashboard_page, "Color code Guide": color_code_guide, "task_1": task_1_page, "practice_page": practice_page, "spelling_practice": spelling_practice_page, "TT_L1_T1_1": TT_L1_T1_1, "TT_L1_T1_2": TT_L1_T1_2, "TT_L1_T1_3": TT_L1_T1_3, "TT_L1_T1_3": TT_L1_T2_1, "TT_L1_T3_1": TT_L1_T3_1, "TT_L1_T4_1": TT_L1_T4_1, "TT_L1_T4_2": TT_L1_T4_2, "TT_L1_T5_1": TT_L1_T5_1, "TT_L1_T5_2": TT_L1_T5_2, "TT_L1_T6_1": TT_L1_T6_1, "TT_L1_T7_1": TT_L1_T7_1, "TT_L1_T8_1": TT_L1_T8_1, "TT_L2_T1_1": TT_L2_T1_1, "TT_L2_T1_2": TT_L2_T1_2, "TT_L2_T1_3": TT_L2_T1_3, "TT_L2_T2_1": TT_L2_T2_1, "TT_L2_T3_1": TT_L2_T3_1, "TT_L2_T3_2": TT_L2_T3_2, "TT_L2_T4_1": TT_L2_T4_1, "TT_L2_T5_1": TT_L2_T5_1, "TT_L2_T6_1": TT_L2_T6_1, "TT_L2_T7_1": TT_L2_T7_1, "TT_L2_T8_1": TT_L2_T8_1, "TT_L2_T8_2": TT_L2_T8_2, "TT_L3_T1_1": TT_L3_T1_1, "TT_L3_T1_2": TT_L3_T1_2, "TT_L3_T1_3": TT_L3_T1_3, "TT_L3_T2_1": TT_L3_T2_1, "TT_L3_T3_1": TT_L3_T3_1, "TT_L3_T4_1": TT_L3_T4_1, "TT_L3_T4_2": TT_L3_T4_2, "TT_L3_T5_1": TT_L3_T5_1, "TT_L3_T6_1": TT_L3_T6_1, "TT_L3_T7_1": TT_L3_T7_1, "TT_L3_T8_1": TT_L3_T8_1, "mode_1_page": mode_1_page, } def main(): # Define all possible pages with display names and internal keys page_names = [ "Login/Registration", "Mode", "Personal Information", "Proficiency Test", "Self-Assessment", "User Dashboard", "Task 1", "Practice", "Spelling Practice", "TT_L1_T1_1", "TT_L1_T1_2", "TT_L1_T1_3", "TT_L1_T2_1", "TT_L1_T3_1", "TT_L1_T4_1", "TT_L1_T4_2", "TT_L1_T5_1", "TT_L1_T5_2", "TT_L1_T6_1", "TT_L1_T7_1", "TT_L1_T8_1", "TT_L2_T1_1", "TT_L2_T1_2", "TT_L2_T1_3", "TT_L2_T2_1", "TT_L2_T3_1", "TT_L2_T3_2", "TT_L2_T4_1", "TT_L2_T5_1", "TT_L2_T6_1", "TT_L2_T7_1", "TT_L2_T8_1", "TT_L2_T8_2", "TT_L3_T1_1", "TT_L3_T1_2", "TT_L3_T1_3", "TT_L3_T2_1", "TT_L3_T3_1", "TT_L3_T4_1", "TT_L3_T4_2", "TT_L3_T5_1", "TT_L3_T6_1", "TT_L3_T7_1", "TT_L3_T8_1", "Color Code Guide", "Learn More", "mode_1_page", "L1_T10_TAS_1", "L2_T10_TAS_1", "L3_T10_TAS_1", ] page_mapping = { "Login/Registration": "login_registration", "Mode": "mode_page", "Personal Information": "personal_info", "Proficiency Test": "proficiency", "Self-Assessment": "self_assessment", "User Dashboard": "user_dashboard", "Task 1": "task_1", "Practice": "practice_page", "Spelling Practice": "spelling_practice", "TT_L1_T1_1": "TT_L1_T1_1", "TT_L1_T1_2": "TT_L1_T1_2", "TT_L1_T1_3": "TT_L1_T1_3", "TT_L1_T2_1": "TT_L1_T2_1", "TT_L1_T3_1": "TT_L1_T3_1", "TT_L1_T4_1": "TT_L1_T4_1", "TT_L1_T4_2": "TT_L1_T4_2", "TT_L1_T5_1": "TT_L1_T5_1", "TT_L1_T5_2": "TT_L1_T5_2", "TT_L1_T6_1": "TT_L1_T6_1", "TT_L1_T7_1": "TT_L1_T7_1", "TT_L1_T8_1": "TT_L1_T8_1", "TT_L2_T1_1": "TT_L2_T1_1", "TT_L2_T1_2": "TT_L2_T1_2", "TT_L2_T1_3": "TT_L2_T1_3", "TT_L2_T2_1": "TT_L2_T2_1", "TT_L2_T3_1": "TT_L2_T3_1", "TT_L2_T3_2": "TT_L2_T3_2", "TT_L2_T4_1": "TT_L2_T4_1", "TT_L2_T5_1": "TT_L2_T5_1", "TT_L2_T6_1": "TT_L2_T6_1", "TT_L2_T7_1": "TT_L2_T7_1", "TT_L2_T8_1": "TT_L2_T8_1", "TT_L2_T8_2": "TT_L2_T8_2", "TT_L3_T1_1": "TT_L3_T1_1", "TT_L3_T1_2": "TT_L3_T1_2", "TT_L3_T1_3": "TT_L3_T1_3", "TT_L3_T2_1": "TT_L3_T2_1", "TT_L3_T3_1": "TT_L3_T3_1", "TT_L3_T4_1": "TT_L3_T4_1", "TT_L3_T4_2": "TT_L3_T4_2", "TT_L3_T5_1": "TT_L3_T5_1", "TT_L3_T6_1": "TT_L3_T6_1", "TT_L3_T7_1": "TT_L3_T7_1", "TT_L3_T8_1": "TT_L3_T8_1", "L1_T10_TAS_1": "L1_T10_TAS_1", "L2_T10_TAS_1": "L2_T10_TAS_1", "L3_T10_TAS_1": "L3_T10_TAS_1", "Color Code Guide": "color_code_guide", "Learn More": "learn_more", "mode_1_page": "mode_1_page" } # Reverse mapping for easy lookup reverse_mapping = {v: k for k, v in page_mapping.items()} # Initialize session state for page if not present if 'page' not in st.session_state: st.session_state.page = "login_registration" # Default page # Initialize other session state variables if 'tt_l1_t1_1_all_correct' not in st.session_state: st.session_state.tt_l1_t1_1_all_correct = False if 'tt_l1_t1_2_all_correct' not in st.session_state: st.session_state.tt_l1_t1_2_all_correct = False # Sidebar Navigation st.sidebar.image('data/logo.png', width=200) st.sidebar.title("Navigation") # Determine the current page's display name current_page_display = reverse_mapping.get(st.session_state.page, "Login/Registration") # Sidebar radio selection with the current page selected by default selected_page_display = st.sidebar.radio( "Go to", page_names, index=page_names.index(current_page_display) if current_page_display in page_names else 0 ) # If the selected page is different from the current page, update the session state if selected_page_display != current_page_display: st.session_state.page = page_mapping[selected_page_display] # Routing based on st.session_state.page if st.session_state.page == "login_registration": login_registration_page() elif st.session_state.page == "mode_page": mode_page() elif st.session_state.page == "personal_info": personal_info_page() elif st.session_state.page == "proficiency": proficiency_page() elif st.session_state.page == "self_assessment": self_assessment_page() elif st.session_state.page == "user_dashboard": user_dashboard_page() elif st.session_state.page == "task_1": task_1_page() elif st.session_state.page == "practice_page": practice_page() elif st.session_state.page == "spelling_practice": spelling_practice_page() elif st.session_state.page == "TT_L1_T1_1": TT_L1_T1_1() elif st.session_state.page == "TT_L1_T1_2": TT_L1_T1_2() elif st.session_state.page == "TT_L1_T1_3": TT_L1_T1_3() elif st.session_state.page == "TT_L1_T2_1": TT_L1_T2_1() elif st.session_state.page == "TT_L1_T3_1": TT_L1_T3_1() elif st.session_state.page == "TT_L1_T4_1": TT_L1_T4_1() elif st.session_state.page == "TT_L1_T4_2": TT_L1_T4_2() elif st.session_state.page == "TT_L1_T5_1": TT_L1_T5_1() elif st.session_state.page == "TT_L1_T5_2": TT_L1_T5_2() elif st.session_state.page == "TT_L1_T6_1": TT_L1_T6_1() elif st.session_state.page == "TT_L1_T7_1": TT_L1_T7_1() elif st.session_state.page == "TT_L1_T8_1": TT_L1_T8_1() elif st.session_state.page == "TT_L2_T1_1": TT_L2_T1_1() elif st.session_state.page == "TT_L2_T1_2": TT_L2_T1_2() elif st.session_state.page == "TT_L2_T1_3": TT_L2_T1_3() elif st.session_state.page == "TT_L2_T2_1": TT_L2_T2_1() elif st.session_state.page == "TT_L2_T3_1": TT_L2_T3_1() elif st.session_state.page == "TT_L2_T3_2": TT_L2_T3_2() elif st.session_state.page == "TT_L2_T4_1": TT_L2_T4_1() elif st.session_state.page == "TT_L2_T5_1": TT_L2_T5_1() elif st.session_state.page == "TT_L2_T6_1": TT_L2_T6_1() elif st.session_state.page == "TT_L2_T7_1": TT_L2_T7_1() elif st.session_state.page == "TT_L2_T8_1": TT_L2_T8_1() elif st.session_state.page == "TT_L2_T8_2": TT_L2_T8_2() elif st.session_state.page == "TT_L3_T1_1": TT_L3_T1_1() elif st.session_state.page == "TT_L3_T1_2": TT_L3_T1_2() elif st.session_state.page == "TT_L3_T1_3": TT_L3_T1_3() elif st.session_state.page == "TT_L3_T2_1": TT_L3_T2_1() elif st.session_state.page == "TT_L3_T3_1": TT_L3_T3_1() elif st.session_state.page == "TT_L3_T4_1": TT_L3_T4_1() elif st.session_state.page == "TT_L3_T4_2": TT_L3_T4_2() elif st.session_state.page == "TT_L3_T5_1": TT_L3_T5_1() elif st.session_state.page == "TT_L3_T6_1": TT_L3_T6_1() elif st.session_state.page == "TT_L3_T7_1": TT_L3_T7_1() elif st.session_state.page == "TT_L3_T8_1": TT_L3_T8_1() elif st.session_state.page == "color_code_guide": color_code_guide() elif st.session_state.page == "learn_more": learn_more_page() elif st.session_state.page == 'success_page': success_page() elif st.session_state.page == 'mode_1_page': mode_1_page() elif st.session_state.page == 'L1_T10_TAS_1': L1_T10_TAS_1() elif st.session_state.page == 'L2_T10_TAS_1': L2_T10_TAS_1() elif st.session_state.page == 'L3_T10_TAS_1': L3_T10_TAS_1() else: st.error("Page not found.") # After the last line in main() preprocess_error_types(session) update_errors_table_with_processed(session) if __name__ == "__main__": main()