import streamlit as st from grammarcorrector import GrammarCorrector import errant import spacy import re import string import json import streamlit.components.v1 as components import random import requests import subprocess import os import math from streamlit_lottie import st_lottie import plotly.express as px from sqlalchemy.ext.mutable import MutableList, MutableDict from collections import defaultdict from sqlalchemy.exc import OperationalError import pandas as pd from collections import defaultdict import time import datetime # Removed MySQL-specific imports: # import mysql.connector # from mysql.connector import Error from sqlalchemy import create_engine, Column, Integer, String, Boolean, ForeignKey, JSON, Time, DateTime from sqlalchemy.orm import sessionmaker, declarative_base # Set page configuration at the top st.set_page_config(page_title="GrammarTool", page_icon="βοΈ") @st.cache_resource def get_spacy_nlp(): try: return spacy.load("en_core_web_sm") except OSError: # Download the model if it's not available subprocess.run(["python", "-m", "spacy", "download", "en_core_web_sm"]) return spacy.load("en_core_web_sm") @st.cache_resource def get_errant_annotator(): return errant.load('en') @st.cache_resource def get_grammar_corrector(): return GrammarCorrector(device="cpu") annotator = get_errant_annotator() grammar = get_grammar_corrector() nlp = get_spacy_nlp() # Load the error codes mapping file_path = os.path.join(os.path.dirname(__file__), 'data/error_code.json') with open(file_path, 'r') as f: error_code_data = json.load(f) @st.cache_data def load_tasks(): file_path = os.path.join(os.path.dirname(__file__), 'data/tasks.json') if not os.path.exists(file_path): st.error(f"Tasks file not found: {file_path}") return {} with open(file_path, 'r', encoding='utf-8') as f: try: tasks_dict = json.load(f) if not tasks_dict: st.error("Tasks file is empty or incorrectly formatted!") return tasks_dict except json.JSONDecodeError: st.error("Error loading tasks.json! Check if the JSON is valid.") return {} @st.cache_data def load_rules(): file_path = os.path.join(os.path.dirname(__file__), 'data/rules.json') with open(file_path, 'r', encoding='utf-8') as f: rules_dict = json.load(f) return rules_dict # Create a dictionary mapping error_code to full_error_name and explanation error_code_mapping = { item['error_code']: { 'full_error_name': item['full_error_name'], 'explanation': item['explanation'], 'css_structure': item['css_structure'] } for item in error_code_data } def load_questions(tutorial_num_list): """Load the JSON and filter tasks by tutorial_num_list (a list of tutorial IDs).""" script_dir = os.path.dirname(os.path.abspath(__file__)) file_path = os.path.join(script_dir, 'data/practice.json') with open(file_path, 'r') as file: tasks = json.load(file) filtered = [task for task in tasks if any(tnum in task["Tutorial number"] for tnum in tutorial_num_list)] return filtered def get_questions_for_tutorial(tasks, num_questions=8): """ Selects a random subset of questions from the loaded tasks. """ questions = tasks.get("Questions", []) if not questions: st.warning("No questions available for this tutorial.") return [] if len(questions) < num_questions: st.info(f"Only {len(questions)} questions available for this tutorial.") return questions else: return random.sample(questions, num_questions) # --- SQLite Connection Setup --- # Instead of connecting to a remote MySQL database, we create a local SQLite database. # The database file (grammar_tool_db.sqlite) will be created in your working directory. engine = create_engine("sqlite:///grammar_tool_db.sqlite", connect_args={"check_same_thread": False}) # Create the session Session = sessionmaker(bind=engine) session = Session() Base = declarative_base() class Student(Base): __tablename__ = 'students' id = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(100), unique=True, nullable=False) email = Column(String(100), unique=True, nullable=False) password = Column(String(255), nullable=False) participate = Column(Boolean, nullable=False) age = Column(Integer) country = Column(String(100)) language = Column(String(45)) main_goal = Column(String(45)) field = Column(String(1000)) preferences = Column(String(100)) proficiency = Column(String(45)) tasks_to_do = Column(JSON) adapt_logs = Column(JSON) mode_1 = Column(String(45)) mode_2 = Column(String(45)) mode_3 = Column(String(45)) class SelfAssessment(Base): __tablename__ = 'self_assessment' student_id = Column(Integer, ForeignKey('students.id'), primary_key=True) skill = Column(String(255), primary_key=True) score = Column(Integer, nullable=False) class TasksDone(Base): __tablename__ = "tasks_done" id = Column(Integer, primary_key=True, autoincrement=True) student_id = Column(Integer, ForeignKey("students.id")) task_id = Column(String(50)) date = Column(DateTime, nullable=True) time = Column(Time, nullable=True) errors = Column(JSON, nullable=True) complexity = Column(JSON, nullable=True) fluency = Column(JSON, nullable=True) processed_errors = Column(JSON, nullable=True) full_text = Column(JSON, nullable=True) class Errors(Base): __tablename__ = "errors" id = Column(Integer, primary_key=True, autoincrement=True) student_id = Column(Integer, ForeignKey("students.id"), nullable=False) gram_err = Column(MutableDict.as_mutable(JSON), nullable=True) spell_err = Column(MutableList.as_mutable(JSON), nullable=True) to_do = Column(MutableList.as_mutable(JSON), nullable=True) done = Column(MutableDict.as_mutable(JSON), nullable=True) # Create all tables (this will create the SQLite database file if it doesn't exist) Base.metadata.create_all(engine) def update_student_mode(student_id, mode_number): """ Update mode_1/mode_2/mode_3 column to 'yes' for the given student. """ student = session.query(Student).filter_by(id=student_id).first() if not student: st.error("User not found in the database.") return if mode_number == 1: student.mode_1 = "yes" elif mode_number == 2: student.mode_2 = "yes" elif mode_number == 3: student.mode_3 = "yes" session.commit() def navigate_to_mode(): st.session_state.page = 'mode_page' def navigate_to_personal_info(): st.session_state.page ='personal_info' def navigate_to_proficiency(): st.session_state.page = 'proficiency' def navigate_to_self_assessment(): st.session_state.page = 'self_assessment' def navigate_to_dashboard(): st.session_state.page ='user_dashboard' def navigate_to_mode_1(): st.session_state.page ='mode_1_page' def navigate_to_spelling(): st.session_state.page ='spelling_practice' def mode_page(): st.title("Choose the Mode") mode_options = { "Adaptive test": 3, # Mode 3 "Mode 1": 1, # Mode 1 "Mode 2 (personalized)": 2 # Mode 2 } selected_mode = st.radio( label="Please select a mode:", options=list(mode_options.keys()), index=0, key="selected_mode" ) if st.button("Next"): mode_number = mode_options.get(selected_mode, None) user_info = st.session_state.get("user_info") if not user_info or not hasattr(user_info, "id"): st.error("No logged-in user found. Please log in first.") return try: update_student_mode(user_info.id, mode_number) except Exception as e: st.error(f"Failed to update mode in DB: {e}") return if mode_number == 3: navigate_to_proficiency() st.rerun() elif mode_number == 1: navigate_to_mode_1() st.rerun() elif mode_number == 2: if not is_personal_info_complete(user_info): st.warning("Redirecting you to Personal Info page...") navigate_to_personal_info() st.rerun() if not is_self_assessment_complete(user_info.id): st.warning("Redirecting you to Self-Assessment page...") navigate_to_self_assessment() st.rerun() if not is_proficiency_complete(user_info): st.warning("Redirecting you to Proficiency Test page...") navigate_to_proficiency() st.rerun() st.success("All steps completed. Redirecting you to the personalized dashboard...") navigate_to_dashboard() st.rerun() st.rerun() def preprocess_error_types(session): tasks = session.query(TasksDone).all() for task in tasks: errors = task.errors if not errors or not errors.get("error_types"): continue processed = [] for e in errors["error_types"]: if isinstance(e, str): processed.append({"error_type": e}) elif isinstance(e, dict): processed.append(e) task.processed_errors = processed session.commit() def update_errors_table_with_processed(session): MAX_RETRIES = 3 RETRY_DELAY = 2 # seconds retries = 0 while retries < MAX_RETRIES: try: with session.no_autoflush: tasks = session.query(TasksDone).filter(TasksDone.processed_errors.isnot(None)).all() if not tasks: return tasks_by_student = defaultdict(list) for task in tasks: tasks_by_student[task.student_id].append(task) for student_id, task_list in tasks_by_student.items(): errors_obj = session.query(Errors).filter_by(student_id=student_id).one_or_none() if not errors_obj: errors_obj = Errors(student_id=student_id, gram_err={}, spell_err=[], to_do=[]) session.add(errors_obj) session.flush() for task in task_list: for e in task.processed_errors: etype = e.get("error_type") if not etype or etype == "OTHER": continue if etype == "SPELL": corr_str = e.get("corrected_string") if corr_str and corr_str not in errors_obj.spell_err: errors_obj.spell_err.append(corr_str) else: if not errors_obj.gram_err: errors_obj.gram_err = {} errors_obj.gram_err[etype] = errors_obj.gram_err.get(etype, 0) + 1 if errors_obj.gram_err: max_count = max(errors_obj.gram_err.values()) errors_obj.to_do = [k for k, v in errors_obj.gram_err.items() if v == max_count] else: errors_obj.to_do = [] session.commit() return except OperationalError as e: session.rollback() retries += 1 if retries < MAX_RETRIES: time.sleep(RETRY_DELAY) else: print(f"Database lock error: {e}, retries exhausted.") raise def insert_student_data( username, email, password, participate, age=None, country=None, language=None, main_goal=None, field=None, preferences=None, proficiency=None ): preferences_clean = remove_emojis(preferences) if preferences else None student = session.query(Student).filter_by(email=email).first() if not student: student = Student( username=username, email=email, password=password, participate=participate, age=age, country=country, language=language, main_goal=main_goal, field=field, preferences=preferences_clean, proficiency=proficiency ) session.add(student) else: student.username = username student.password = password student.participate = participate student.age = age student.country = country student.language = language student.main_goal = main_goal student.field = field student.preferences = preferences_clean student.proficiency = proficiency session.commit() def fetch_student_data(username): return session.query(Student).filter_by(username=username).first() def insert_self_assessment_data(student_id, skill, score): assessment = SelfAssessment( student_id=student_id, skill=skill, score=score ) session.add(assessment) session.commit() def fetch_self_assessments(student_id): return session.query(SelfAssessment).filter_by(student_id=student_id).all() def handle_login(username, password): user = fetch_student_data(username) if user and password == user.password: st.session_state.user_info = user st.session_state.student_id = user.id return True else: st.error("Invalid username or password.") return False def handle_signup(username, email, password, participate_in_research): insert_student_data( username=username, email=email, password=password, participate=participate_in_research ) user = fetch_student_data(username) if user: st.session_state.user_info = user st.session_state.page = "personal_info" else: st.error("Signup failed. Please try again.") def handle_forgot_password(email): user = session.query(Student).filter_by(email=email).first() if user: st.success(f"A password reset link has been sent to {email}.") else: st.error("No account found with that email.") def remove_emojis(text): if not text: return text emoji_pattern = re.compile( "[" "\U0001F600-\U0001F64F" # emoticons "\U0001F300-\U0001F5FF" # symbols & pictographs "\U0001F680-\U0001F6FF" # transport & map symbols "\U0001F1E0-\U0001F1FF" # flags (iOS) "\U00002500-\U00002BEF" # various symbols "\U00002702-\U000027B0" # dingbats "\U0001F900-\U0001F9FF" # supplemental symbols "\U0001FA70-\U0001FAFF" # symbols extended-A "\U00002600-\U000026FF" # miscellaneous symbols "]+", flags=re.UNICODE ) return emoji_pattern.sub(r'', text).strip() def personal_info_page(): render_progress_bar() st.title("π€ Personal Information") age_text = st.text_input("Age:", value="") age = int(age_text) if age_text.isdigit() else None country = st.text_input("Country:") native_language = st.text_input("Native Language:") main_goal = st.selectbox("π― Main Goal:", ["", "Work", "Study", "General Communication"]) field_value = None if main_goal == "Work": work_options = st.selectbox( "Select Work Option:", ["", "Healthcare π©Ί", "Engineering π οΈ", "Business π", "Education π"] ) field_value = remove_emojis(work_options) if work_options else None elif main_goal == "Study": study_options = st.selectbox( "Select Study Option:", ["", "High school", "Bachelor's degree", "Master's degree", "Doctorate/PhD"] ) if study_options in ["Bachelor's degree", "Master's degree", "Doctorate/PhD"]: major_options = st.selectbox( "Select Your Major:", ["", "Medicine π©Ί", "Science and Technology π¬", "Business and Finance π¦", "Arts and Humanitiesπ"] ) if major_options: study_combined = f"{study_options}, Major: {remove_emojis(major_options)}" else: study_combined = study_options else: study_combined = study_options field_value = remove_emojis(study_combined) if study_combined else None interests = st.multiselect("Interests:", ["Travel βοΈ", "Sports β½οΈ", "Movies π¬"]) stripped_interests = [remove_emojis(i) for i in interests] preferences_value = ", ".join(stripped_interests) if stripped_interests else None if st.button("Continue"): user_info = st.session_state.user_info if user_info and hasattr(user_info, 'id'): insert_student_data( username=user_info.username, email=user_info.email, password=user_info.password, participate=user_info.participate, age=age, country=country, language=native_language, main_goal=main_goal, field=field_value, preferences=preferences_value ) st.success("Information saved successfully.") navigate_to_self_assessment() st.rerun() else: st.error("Error: User information is missing.") # The rest of your functions (e.g., load_item_bank, adaptive test functions, etc.) # remain unchanged. def load_item_bank(): file_path = 'data/item_bank.json' with open(file_path, "r", encoding="utf-8") as f: data = json.load(f) for item in data: if "id" not in item or "level" not in item or "question" not in item or "answer" not in item: print(f"Missing keys in item: {item}") if item["level"] not in ["easy", "medium", "high"]: print(f"Invalid level in item: {item}") item["level"] = "medium" random.shuffle(data) return data def prob_correct(item_level, participant_level): item_level = int(item_level) participant_level = int(participant_level) if participant_level > item_level: return 0.95 elif participant_level == item_level: return 0.6 else: return 0.25 def calculate_entropy(belief): return -sum(prob * math.log2(prob) for prob in belief.values() if prob > 0) def expected_entropy(question, belief, item_level, level_map): prob_correct_ans = sum(prob_correct(item_level, level_map[level]) * belief[level] for level in belief) prob_incorrect_ans = 1 - prob_correct_ans belief_correct = {lvl: belief[lvl] * prob_correct(item_level, level_map[lvl]) for lvl in belief} belief_incorrect = {lvl: belief[lvl] * (1 - prob_correct(item_level, level_map[lvl])) for lvl in belief} total_correct = sum(belief_correct.values()) total_incorrect = sum(belief_incorrect.values()) for lvl in belief_correct: belief_correct[lvl] /= total_correct or 1 belief_incorrect[lvl] /= total_incorrect or 1 entropy_correct = calculate_entropy(belief_correct) entropy_incorrect = calculate_entropy(belief_incorrect) return prob_correct_ans * entropy_correct + prob_incorrect_ans * entropy_incorrect def select_question(belief, item_bank, asked_questions, level_map): min_entropy = float("inf") best_question = None for item in item_bank: if item["id"] not in asked_questions: item_level = level_map[item["level"]] exp_entropy = expected_entropy(item, belief, item_level, level_map) if exp_entropy < min_entropy: min_entropy = exp_entropy best_question = item return best_question def update_belief(belief, item_level, correct, level_map): for lvl in belief: likelihood = prob_correct(item_level, level_map[lvl]) if correct else (1 - prob_correct(item_level, level_map[lvl])) belief[lvl] *= likelihood total = sum(belief.values()) for lvl in belief: belief[lvl] /= total or 1 def stop_criterion(belief, threshold=0.9, min_questions=10, total_questions=0, max_questions=30): if total_questions < min_questions: return False if max(belief.values()) >= threshold: return True if total_questions >= max_questions: return True return False def go_next(): st.session_state.current_question = None st.session_state.submitted_answer = None st.session_state.phase = "answering" if stop_criterion( st.session_state.belief, min_questions=10, threshold=0.9, total_questions=st.session_state.total_questions, max_questions=30, ): st.session_state.completed = True import streamlit as st import time import datetime # ... your other imports ... def proficiency_page(): st.title("Adaptive Grammar Test") # Check if user is logged in user_info = st.session_state.get("user_info") if not user_info or not hasattr(user_info, "id"): st.error("User ID is missing. Please log in to take the test.") return # Detect the mode user came from (saved in session_state) mode_number = st.session_state.get("mode_number", None) # Retrieve the student ID from user_info student_id = user_info.id # Load item bank item_bank = load_item_bank() # Levels must match what's in your item bank levels = ["pre", "easy", "medium", "high", "post"] level_map = {level: i for i, level in enumerate(levels)} # ------------------------- # 1) INIT SESSION STATE # ------------------------- if "belief" not in st.session_state: st.session_state.belief = {level: 1 / len(levels) for level in levels} st.session_state.asked_questions = set() st.session_state.current_question = None st.session_state.submitted_answer = None st.session_state.total_questions = 0 st.session_state.history = [] st.session_state.completed = False st.session_state.question_number = 0 # ---------------------------- # 2) IF TEST IS ALREADY COMPLETE # ---------------------------- if st.session_state.completed: st.success("Test Completed!") final_level = max(st.session_state.belief, key=st.session_state.belief.get) st.write(f"Your estimated proficiency level: **{final_level.capitalize()}**") # Save to DB student = session.query(Student).filter_by(id=student_id).first() if student: student.adapt_logs = st.session_state.history student.proficiency = final_level # 3) Update tasks_to_do according to final_level tasks_list = TASKS_TO_DATABASE.get(final_level.lower(), []) student.tasks_to_do = json.dumps(tasks_list) session.commit() session.commit() st.success("Your adaptive test logs and final proficiency level have been saved to the database.") if st.button("Next"): # If they came from mode 2, proceed to personal dashboard navigate_to_dashboard() # or navigate_to_personal_dashboard() st.rerun() else: st.error(f"No student found in DB with id = {student_id}. Cannot save test results.") return # Exit function to prevent further questions from being displayed # ------------------------- # 3) LOAD A NEW QUESTION # ------------------------- if st.session_state.current_question is None: st.session_state.total_questions += 1 st.session_state.question_number = st.session_state.total_questions next_q = select_question( st.session_state.belief, item_bank, st.session_state.asked_questions, level_map ) # If no question returned, mark test as completed if not next_q: st.session_state.completed = True st.rerun() return # Save the newly selected question st.session_state.current_question = next_q st.session_state.submitted_answer = None question = st.session_state.current_question # ------------------------- # 4) DISPLAY THE QUESTION # ------------------------- st.write(f"**Question {st.session_state.question_number}:** {question['question']}") # Let the user pick an answer question_key = f"selected_answer_{question['id']}" selected_answer = st.radio("", question["options"], key=question_key, index=None) # One combined button to submit answer & go to next if st.button("Submit & Next"): if selected_answer is None: st.warning("Please select an answer before submitting.") st.stop() # Stop execution here so user can pick an answer. correct = (selected_answer == question["answer"]) before_belief = st.session_state.belief.copy() update_belief( st.session_state.belief, level_map[question["level"]], correct, level_map ) after_belief = st.session_state.belief.copy() # Record history st.session_state.history.append({ "question_id": question["id"], "level": question["level"], "question": question["question"], "user_answer": selected_answer, "correct": correct, "belief_before": before_belief, "belief_after": after_belief, "current_number": st.session_state.question_number, }) # Mark this question as asked st.session_state.asked_questions.add(question["id"]) # Check stopping criteria if stop_criterion( st.session_state.belief, min_questions=10, threshold=0.9, total_questions=st.session_state.total_questions, max_questions=30, ): st.session_state.completed = True st.rerun() return # Otherwise, clear so we load the next question st.session_state.current_question = None st.session_state.submitted_answer = None st.rerun() def fetch_tasks(student_id): """ Fetch the tasks assigned to a student. """ student = session.query(Student).filter_by(id=student_id).first() if student and student.tasks_to_do: return json.loads(student.tasks_to_do) # Convert JSON string back to list return [] def self_assessment_page(): render_progress_bar() st.title("Self-Assessment") st.header("Rate your difficulty for each skill (1 = very easy, 10 = very difficult)") skills = { 'Word Order': 'slider_word_order', 'Tenses': 'slider_tenses', 'Complex Sentence': 'slider_complex_sentence', 'Punctuation': 'slider_punctuation', 'Prepositions': 'slider_prepositions', 'Spelling': 'slider_spelling' } skill_scores = {} for skill, key in skills.items(): if key not in st.session_state: st.session_state[key] = 1 skill_scores[key] = st.slider(skill, 1, 10, key=key, value=st.session_state[key]) if st.button("Submit Assessment"): user_info = st.session_state.user_info if user_info and hasattr(user_info, 'id'): for skill, key in skills.items(): score = skill_scores[key] insert_self_assessment_data( student_id=user_info.id, skill=skill, score=score ) st.success("Self-assessment submitted!") navigate_to_proficiency() st.rerun() else: st.error("Error: User information is missing.") def is_personal_info_complete(student: Student) -> bool: """ Decide what 'complete' means for personal info. For example, check if age, country, language are set. Adjust the logic to your actual definition of 'complete'. """ # Example: If any of these are None, personal info isn't complete if not student.age or not student.country or not student.language: return False return True def is_self_assessment_complete(student_id: int) -> bool: """ Check if the user has at least one entry in the self_assessment table. Or you could check for a minimum set of skill entries, etc. """ entries = session.query(SelfAssessment).filter_by(student_id=student_id).all() return len(entries) > 0 def is_proficiency_complete(student: Student) -> bool: """ For proficiency, assume it's 'complete' if student.proficiency is not None or empty. Adjust as needed. """ return bool(student.proficiency) # True if student.proficiency is non-empty def login_registration_page(): render_progress_bar() st.title("Login / Sign Up") tabs = st.tabs(["Login", "Sign Up"]) # Login Tab with tabs[0]: st.header("Welcome Back!") username = st.text_input("π€ Username:", placeholder="Your username", key="login_username_input_unique_1") password = st.text_input("π Password:", type="password", placeholder="Your password", key="login_password_input_unique_1") if st.button("π Log In", key="login_button_unique_1"): if handle_login(username, password): # Validate login against the `students` table student = fetch_student_data(username) if student: st.session_state.user_info = student st.success("Logged in successfully!") navigate_to_mode() st.rerun() else: st.error("Failed to fetch user data. Please try again.") else: st.error("Invalid username or password.") # Sign-Up Tab with tabs[1]: st.header("Create a New Account") new_username = st.text_input("π€ Choose a Username:", placeholder="Pick a unique username", key="signup_username_input_unique_1") email = st.text_input("βοΈ Email:", placeholder="Your email address", key="signup_email_input_unique_1") new_password = st.text_input("π Set a Password:", type="password", placeholder="Choose a strong password", key="signup_password_input_unique_1") participate = st.checkbox("I agree to participate in research", key="research_checkbox_unique_1") agree_to_pdpa = st.checkbox("I agree to the Personal Data Protection Policy (PDPA)", key="pdpa_checkbox_unique_1") show_pdpa_policy() if st.button("π Create My Account", key="signup_button_unique_1"): if not agree_to_pdpa: st.error("You must agree to the PDPA to sign up.") elif fetch_student_data(new_username): # Check if username already exists st.error("Username already taken. Please choose another.") else: # Insert new student data insert_student_data( username=new_username, email=email, password=new_password, # In production, hash passwords before storing participate=participate ) student = fetch_student_data(new_username) if student: st.session_state.user_info = student st.success("Your account has been created! Redirecting...") navigate_to_mode() st.rerun() else: st.error("An error occurred during sign-up. Please try again later.") TASKS_TO_DATABASE = { "pre": [ "L1_T1_TAS_1", "TT_L1_T1_1", "TT_L1_T1_2", "TT_L1_T1_3", "L1_T2_TAS_1", "TT_L1_T2_1", "L1_T3_TAS_1", "TT_L1_T3_1", "L1_T4_TAS_1", "TT_L1_T4_1", "TT_L1_T4_2", "L1_T5_TAS_1", "TT_L1_T5_1", "TT_L1_T5_2", "L1_T6_TAS_1", "TT_L1_T6_1", "L1_T7_TAS_1", "TT_L1_T7_1", "L1_T8_TAS_1", "TT_L1_T8_1", "L1_T9_TAS_1", "L1_T10_TAS_1" ], "easy": [ "L1_T1_TAS_1", "TT_L1_T1_1", "TT_L1_T1_2", "TT_L1_T1_3", "L1_T2_TAS_1", "TT_L1_T2_1", "L1_T3_TAS_1", "TT_L1_T3_1", "L1_T4_TAS_1", "TT_L1_T4_1", "TT_L1_T4_2", "L1_T5_TAS_1", "TT_L1_T5_1", "TT_L1_T5_2", "L1_T6_TAS_1", "TT_L1_T6_1", "L1_T7_TAS_1", "TT_L1_T7_1", "L1_T8_TAS_1", "TT_L1_T8_1", "L1_T9_TAS_1", "L1_T10_TAS_1" ], "medium": [ "L2_T1_TAS_1", "TT_L2_T1_1", "TT_L2_T1_2", "TT_L2_T1_3", "L2_T2_TAS_1", "TT_L2_T2_1", "L2_T3_TAS_1", "TT_L2_T3_1", "TT_L2_T3_2", "L2_T4_TAS_1", "TT_L2_T4_1", "L2_T5_TAS_1", "TT_L2_T5_1", "L2_T6_TAS_1", "TT_L2_T6_1", "L2_T7_TAS_1", "TT_L2_T7_1", "L2_T8_TAS_1", "TT_L2_T8_1", "TT_L2_T8_2", "L2_T9_TAS_1", "L2_T10_TAS_1" ], "high": [ "L3_T1_TAS_1", "TT_L3_T1_1", "TT_L3_T1_2", "TT_L3_T1_3", "L3_T2_TAS_1", "TT_L3_T2_1", "L3_T3_TAS_1", "TT_L3_T3_1", "L3_T4_TAS_1", "TT_L3_T4_1", "TT_L3_T4_2", "L3_T5_TAS_1", "TT_L3_T5_1", "L3_T6_TAS_1", "TT_L3_T6_1", "L3_T7_TAS_1", "TT_L3_T7_1", "L3_T8_TAS_1", "TT_L3_T8_1", "L3_T9_TAS_1", "L3_T10_TAS_1" ], "post": [ "L3_T1_TAS_1", "TT_L3_T1_1", "TT_L3_T1_2", "TT_L3_T1_3", "L3_T2_TAS_1", "TT_L3_T2_1", "L3_T3_TAS_1", "TT_L3_T3_1", "L3_T4_TAS_1", "TT_L3_T4_1", "TT_L3_T4_2", "L3_T5_TAS_1", "TT_L3_T5_1", "L3_T6_TAS_1", "TT_L3_T6_1", "L3_T7_TAS_1", "TT_L3_T7_1", "L3_T8_TAS_1", "TT_L3_T8_1", "L3_T9_TAS_1", "L3_T10_TAS_1" ] } def get_next_undone_task(student_id): """ Fetch the next undone task by comparing 'tasks_to_do' with 'tasks_done', while treating all tasks in an optional group as completed if one is done. """ student = session.query(Student).filter_by(id=student_id).first() if not student: st.error("User not found in the database.") return None # Fetch tasks assigned to the student try: tasks_to_do = json.loads(student.tasks_to_do) if student.tasks_to_do else [] except json.JSONDecodeError: st.error("Error parsing tasks_to_do. Invalid JSON format.") return None # Fetch completed tasks completed_tasks = session.query(TasksDone.task_id).filter_by(student_id=student_id).all() completed_tasks = {task[0] for task in completed_tasks} # Convert to set for fast lookup # Define optional task groups OPTIONAL_TASK_GROUPS = { "L1_T2_TAS_1": ["L1_T2_TAS_1", "L1_T2_TAS_2"], "L1_T3_TAS_1": ["L1_T3_TAS_1", "L1_T3_TAS_2", "L1_T3_TAS_3"], "L1_T4_TAS_1": ["L1_T4_TAS_1", "L1_T4_TAS_2"], "L1_T5_TAS_1": ["L1_T5_TAS_1", "L1_T5_TAS_2", "L1_T5_TAS_3"], "L1_T6_TAS_1": ["L1_T6_TAS_1", "L1_T6_TAS_2", "L1_T6_TAS_3"], "L1_T7_TAS_1": ["L1_T7_TAS_1", "L1_T7_TAS_2"], "L1_T8_TAS_1": ["L1_T8_TAS_1", "L1_T8_TAS_2", "L1_T8_TAS_3"], "L1_T9_TAS_1": ["L1_T9_TAS_1", "L1_T9_TAS_2", "L1_T9_TAS_3"], "L2_T2_TAS_1": ["L2_T2_TAS_1", "L2_T2_TAS_2", "L2_T2_TAS_3"], "L2_T3_TAS_1": ["L2_T3_TAS_1", "L2_T3_TAS_2", "L2_T3_TAS_3"], "L2_T4_TAS_1": ["L2_T4_TAS_1", "L2_T4_TAS_2", "L2_T4_TAS_3"], "L2_T5_TAS_1": ["L2_T5_TAS_1", "L2_T5_TAS_2", "L2_T5_TAS_3"], "L2_T6_TAS_1": ["L2_T6_TAS_1", "L2_T6_TAS_2"], "L2_T7_TAS_1": ["L2_T7_TAS_1", "L2_T7_TAS_2", "L2_T7_TAS_3"], "L2_T8_TAS_1": ["L2_T8_TAS_1", "L2_T8_TAS_2", "L2_T8_TAS_3"], "L2_T9_TAS_1": ["L2_T9_TAS_1", "L2_T9_TAS_2"], "L3_T2_TAS_1": ["L3_T2_TAS_1", "L3_T2_TAS_2", "L3_T2_TAS_3"], "L3_T3_TAS_1": ["L3_T3_TAS_1", "L3_T3_TAS_2", "L3_T3_TAS_3"], "L3_T4_TAS_1": ["L3_T4_TAS_1", "L3_T4_TAS_2"], "L3_T5_TAS_1": ["L3_T5_TAS_1", "L3_T5_TAS_2", "L3_T5_TAS_3"], "L3_T6_TAS_1": ["L3_T6_TAS_1", "L3_T6_TAS_2", "L3_T6_TAS_3"], "L3_T7_TAS_1": ["L3_T7_TAS_1", "L3_T7_TAS_2", "L3_T7_TAS_3"], "L3_T8_TAS_1": ["L3_T8_TAS_1", "L3_T8_TAS_2"], "L3_T9_TAS_1": ["L3_T9_TAS_1", "L3_T9_TAS_2"], } # Mark optional groups as completed if one task in the group is done completed_task_groups = set() for key_task, task_group in OPTIONAL_TASK_GROUPS.items(): if any(task in completed_tasks for task in task_group): # If any task in the group is done completed_task_groups.update(task_group) # Mark all as completed # Find the first undone task for task in tasks_to_do: if isinstance(task, list): # Ignore task groups continue if task not in completed_tasks and task not in completed_task_groups: return task return None # No remaining tasks def start_next_activity(): """ Handles logic for "Start the activity". Redirects to the next undone task or tutorial page. """ user_info = st.session_state.get('user_info') if not user_info or not hasattr(user_info, 'id'): st.error("No logged-in user found. Please log in first.") return next_task = get_next_undone_task(user_info.id) if next_task: if next_task in pages: # β If it's a tutorial page, go directly to it st.session_state.page = next_task else: # β If it's a normal task, go to `task_1` and store the task st.session_state.task_selection = next_task st.session_state.page = "task_1" st.rerun() else: st.success("π All assigned tasks have been completed!") def user_dashboard_page(): st.title("User Dashboard") # Custom CSS Styles styles = """ """ st.markdown(styles, unsafe_allow_html=True) # Retrieve logged-in student data student = st.session_state.get('user_info') # Ensure 'user_info' contains the student object if not student: st.error("No user is currently logged in.") return st.markdown(f"
{words_count}
{sentences_count}
{learning_time} minutes
Introduction
At GrammarTool, we are committed to protecting the privacy and personal data of our users...
The personal data we collect will be used for the following purposes:
We implement appropriate technical and organizational measures to protect your personal data from unauthorized access...
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.
{selected_task["task"]}
{example_text}
π 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['accuracy_feedback']}
{st.session_state['complexity_feedback']}
{st.session_state['fluency_feedback']}
Error {error_id + 1}: {full_error_name}
{explanation}
Your writing looks good! No grammar errors were detected.
{st.session_state['accuracy_feedback']}
{st.session_state['complexity_feedback']}
{st.session_state['fluency_feedback']}
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("Your writing looks good! No grammar errors were detected.
{corrected_text}
{selected_task["task"]}
π 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"Drag and drop the correct noun into the blank space to complete the sentence.
Drag and drop the correct verb into the blank space to complete the sentence.
Use the dropdown lists below to explore different parts of speech.
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.
Practice combining noun and verb phrases by dragging and dropping them into the correct order to form sentences related to work and studies.
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.
Complete the following sentences by selecting the correct verb form. Be careful with the tenses!
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.
Select the correct verb form and adverb to complete the sentences.
Here are some examples of sentences using Present Continuous:
Select the correct verb form and adverb to complete the sentences.
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".
Here are some examples of noun phrases with different quantifiers:
Drag the nouns into the correct columns: Countable or Uncountable.
Select the appropriate quantifier to complete the sentences.
Prepositions of Time: These prepositions indicate when something happens. Common prepositions of time include:
Prepositions of Place: These prepositions indicate the location of something. Common prepositions of place include:
The prepositional phrases are highlighted in light purple.
Example:
Here are some examples of sentences using prepositions of time and place:
Fill in the blanks with the correct preposition of time or place.
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.
Verbs Followed by Gerunds | Verbs Followed by Infinitives |
---|---|
enjoy | want |
mind | need |
suggest | decide |
avoid | hope |
finish | learn |
Here are some examples of sentences with gerunds and infinitives:
Select the correct gerund or infinitive form to complete each sentence.
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:
Use a comma before the coordinating conjunction to connect the two clauses.
For example:
She likes reading, and he enjoys writing.
Create your own compound sentence using the dropdown lists below. Choose subjects, verbs, and actions to form a sentence with gerunds or infinitives.
In this lesson, we will explore the Past Simple and Past Continuous tenses, focusing on simple sentences and keywords.
Example:
Example:
Choose the correct form of the verb to complete the sentence:
In this lesson, we will explore the difference between using "when" and "while" in sentences with Past Simple and Past Continuous tenses.
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.
Drag and drop to match the first half of the sentence with the correct second half.
Future Simple ("will"):
"To be going to":
For each sentence, choose the correct form of the verb ("will" or "to be going to") according to the rules.
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.
Here are some examples of sentences in active and passive voice:
Drag the sentences into the correct columns: Active Voice or Passive Voice.
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."
Select the correct modal verb to complete each sentence.
Use the dropdown lists below to explore different parts of speech.
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.
Practice combining noun and verb phrases by dragging and dropping them into the correct order to form sentences related to work and studies.
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.
The Zero Conditional is used to talk about things that are always true or very likely to happen. The structure is:
The First Conditional is used to talk about a likely or possible result in the future. The structure is:
Select the appropriate verb to complete the conditional sentences.
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.
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.
Drag and drop to match the first half of the sentence with the correct second half.
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.
Drag and drop the correct verb forms into the blanks to complete the sentences.
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:
When to Use Infinitives:
Special Cases:
Here are some examples of sentences with gerunds and infinitives:
Select the correct form of the verb to complete each sentence.
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.
Create compound sentences using the dropdown lists below. The sentences should correctly match subjects with verbs, gerunds, and infinitives.
Drag the correct sentence half into the appropriate blank to complete the compound sentences.
Relative Clauses: Relative clauses are used to give additional information about a noun. They start with a relative pronoun and function as an adjective.
Select the correct relative pronoun to complete each sentence.
Subordinate Clauses: Subordinate clauses add detail to the main clause and begin with subordinating conjunctions such as "because," "when," "since," etc.
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."
Drag and drop to match the first half of the sentence (main clause) with the correct second half (subordinate clause).
Select the appropriate subordinating conjunction to complete the sentence correctly.
The second conditional sentence is used for imagined situations, often in advice or opinion-giving. It talks about hypothetical scenarios and their possible outcomes.
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."
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.
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.
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."
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.
Reported speech is used to tell someone what another person said. It involves changes in pronouns, tense, and sometimes word order.
Remember to change the pronouns and shift the tense back when reporting speech. For example, "I am going" changes to "she was going."
Direct Speech: "I am going to the market."
Direct Speech: "Did you finish your homework?"
Direct Speech: "Where do you live?"
Drag and drop to match the direct speech with the correct reported speech.
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:
Direct Speech (Tense) | Reported Speech (Tense) |
---|---|
Present Simple | Past Simple |
Present Continuous | Past Continuous |
Present Perfect | Past Perfect |
Past Simple | Past Perfect |
Will | Would |
Direct Speech (Key Word) | Reported Speech (Key Word) |
---|---|
Today | That day |
Now | Then |
Here | There |
Yesterday | The day before |
Tomorrow | The next day |
Below are sentences in direct speech. Transform them into reported speech by selecting the correct options from the dropdowns.
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.
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:
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:
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.
The Third Conditional is used to talk about imagined situations in the past, often involving regret. The structure is:
Complete the sentences by selecting the correct options.
{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("""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.
Drag and drop to match the first half of the sentence with the correct second half.
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.
Let's explore how these conjunctions are used in sentences:
When the subordinate clause comes before the main clause, a comma is usually used to separate the two clauses. For example:
However, when the subordinate clause follows the main clause, no comma is usually needed:
Select the appropriate subordinating conjunction to complete each sentence correctly.
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:
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:
I have been working on this project for two hours.
They have been traveling around the world since last year.
He has not been feeling well lately.
Have you been studying English for a long time?
Select the correct verb form to complete each sentence.
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:
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:
Some verbs are naturally followed by the gerund form.
Examples:
Select the correct form (gerund or infinitive) to complete each sentence.
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:
Drag and drop the phrases into the correct order to form meaningful sentences. Each drop zone corresponds to one sentence.
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.
Select the correct relative pronoun to complete each sentence.
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.
They will start the project tomorrow.
They will be working on the project at this time tomorrow.
They will have completed the project by next week.
Drag and drop the correct verb phrases into the blanks to complete the sentences.
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.
They might have forgotten the meeting.
She must be sleeping now.
Drag and drop the correct phrases into the sentences to complete them.
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.
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.
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.
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.
Never have I experienced such a situation.
No sooner had they left than the storm started.
Not only did she finish the project, but also she presented it perfectly.
Drag and drop the phrases into the correct order to form meaningful sentences with inversion.
You've successfully completed the program. Your dedication and hard work have paid off!
Here's what you achieved:
Keep up the great work and continue striving for excellence!
Thank you for using our application. We wish you continued success in your learning journey!
- Noun
- Verb
- Adjective
- Adverb
- Determiner
- Conjunction
- Noun Phrase
- Verb Phrase
- Prepositional Phrase
- Subject
- Verb
- Object
- Main Clause
- Subordinate Clause