diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,21 +1,15429 @@ 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 -# Initialize the GrammarCorrector -corrector = GrammarCorrector() +# Set page configuration at the top +st.set_page_config(page_title="GrammarTool", page_icon="✏️") -# Streamlit app title -st.title("Grammar Correction App") +@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") -# Input text area -user_input = st.text_area("Enter text to correct:", height=200) +@st.cache_resource +def get_errant_annotator(): + return errant.load('en') -# Button to trigger correction -if st.button("Correct Grammar"): - if user_input: - # Perform grammar correction - corrected_text = corrector.correct(user_input) - st.subheader("Corrected Text:") - st.write(corrected_text) +@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 GerundsVerbs Followed by Infinitives
    enjoywant
    mindneed
    suggestdecide
    avoidhope
    finishlearn
    +
    +
    +
    + """.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 SimplePast Simple
    Present ContinuousPast Continuous
    Present PerfectPast Perfect
    Past SimplePast Perfect
    WillWould
    +

    Key Word Changes in Reported Speech:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Direct Speech (Key Word)Reported Speech (Key Word)
    TodayThat day
    NowThen
    HereThere
    YesterdayThe day before
    TomorrowThe 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.warning("Please enter text to correct.") + 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() \ No newline at end of file