# === 1. IMPORTS & SETUP === import streamlit as st import json import datetime import logging from openai import OpenAI from typing import Dict, List, Set, Tuple import io from reportlab.lib import colors from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer from datetime import datetime import random # === 2. CONFIGURATIONS === # Theme Configuration story_themes = { 'fantasy': { 'id': 'fantasy', 'icon': '🏰', 'name_en': 'Fantasy & Magic', 'name_th': 'แฟนตาซีและเวทมนตร์', 'description_th': 'ผจญภัยในโลกแห่งเวทมนตร์และจินตนาการ', 'description_en': 'Adventure in a world of magic and imagination', 'level_range': ['Beginner', 'Intermediate', 'Advanced'], 'vocabulary': { 'Beginner': ['dragon', 'magic', 'wand', 'spell', 'wizard', 'fairy', 'castle', 'king', 'queen'], 'Intermediate': ['potion', 'enchanted', 'castle', 'creature', 'power', 'scroll', 'portal', 'magical'], 'Advanced': ['sorcery', 'mystical', 'enchantment', 'prophecy', 'ancient', 'legendary', 'mythical'] }, 'story_starters': { 'Beginner': [ {'th': 'วันหนึ่งฉันเจอไม้วิเศษในสวน...', 'en': 'One day, I found a magic wand in the garden...'}, {'th': 'มังกรน้อยกำลังมองหาเพื่อน...', 'en': 'The little dragon was looking for a friend...'}, {'th': 'เจ้าหญิงน้อยมีความลับวิเศษ...', 'en': 'The little princess had a magical secret...'} ], 'Intermediate': [ {'th': 'ในปราสาทเก่าแก่มีประตูลึกลับ...', 'en': 'In the ancient castle, there was a mysterious door...'}, {'th': 'เมื่อน้ำยาวิเศษเริ่มส่องแสง...', 'en': 'When the magic potion started to glow...'}, {'th': 'หนังสือเวทมนตร์เล่มนั้นเปิดออกเอง...', 'en': 'The spellbook opened by itself...'} ], 'Advanced': [ {'th': 'คำทำนายโบราณกล่าวถึงผู้วิเศษที่จะมา...', 'en': 'The ancient prophecy spoke of a wizard who would come...'}, {'th': 'ในโลกที่เวทมนตร์กำลังจะสูญหาย...', 'en': 'In a world where magic was fading away...'}, {'th': 'ณ จุดบรรจบของดวงดาวทั้งห้า...', 'en': 'At the convergence of the five stars...'} ] }, 'background_color': '#E8F3FF', 'accent_color': '#1E88E5' }, 'nature': { 'id': 'nature', 'icon': '🌳', 'name_en': 'Nature & Animals', 'name_th': 'ธรรมชาติและสัตว์โลก', 'description_th': 'เรื่องราวของสัตว์น้อยใหญ่และธรรมชาติอันงดงาม', 'description_en': 'Stories of animals and beautiful nature', 'level_range': ['Beginner', 'Intermediate', 'Advanced'], 'vocabulary': { 'Beginner': ['tree', 'bird', 'flower', 'cat', 'dog', 'garden', 'rabbit', 'butterfly', 'sun'], 'Intermediate': ['forest', 'river', 'mountain', 'wildlife', 'season', 'weather', 'rainbow', 'stream'], 'Advanced': ['ecosystem', 'habitat', 'wilderness', 'environment', 'conservation', 'migration', 'climate'] }, 'story_starters': { 'Beginner': [ {'th': 'แมวน้อยเจอนกในสวน...', 'en': 'The little cat found a bird in the garden...'}, {'th': 'ดอกไม้สวยกำลังเบ่งบาน...', 'en': 'The beautiful flower was blooming...'}, {'th': 'กระต่ายน้อยหลงทางในสวน...', 'en': 'The little rabbit got lost in the garden...'} ], 'Intermediate': [ {'th': 'ในป่าใหญ่มีเสียงลึกลับ...', 'en': 'In the big forest, there was a mysterious sound...'}, {'th': 'แม่น้ำสายนี้มีความลับ...', 'en': 'This river had a secret...'}, {'th': 'สายรุ้งพาดผ่านภูเขา...', 'en': 'A rainbow stretched across the mountain...'} ], 'Advanced': [ {'th': 'ฝูงนกกำลังอพยพย้ายถิ่น...', 'en': 'The birds were migrating...'}, {'th': 'ป่าฝนกำลังเปลี่ยนแปลง...', 'en': 'The rainforest was changing...'}, {'th': 'ความลับของระบบนิเวศ...', 'en': 'The secret of the ecosystem...'} ] }, 'background_color': '#F1F8E9', 'accent_color': '#4CAF50' }, 'space': { 'id': 'space', 'icon': '🚀', 'name_en': 'Space Adventure', 'name_th': 'ผจญภัยในอวกาศ', 'description_th': 'เรื่องราวการสำรวจอวกาศและดวงดาวอันน่าตื่นเต้น', 'description_en': 'Exciting stories of space exploration and celestial discoveries', 'level_range': ['Beginner', 'Intermediate', 'Advanced'], 'vocabulary': { 'Beginner': ['star', 'moon', 'planet', 'sun', 'rocket', 'alien', 'space', 'light'], 'Intermediate': ['astronaut', 'spacecraft', 'galaxy', 'meteor', 'satellite', 'orbit', 'comet'], 'Advanced': ['constellation', 'nebula', 'astronomy', 'telescope', 'exploration', 'discovery'] }, 'story_starters': { 'Beginner': [ {'th': 'จรวดลำน้อยพร้อมบินแล้ว...', 'en': 'The little rocket was ready to fly...'}, {'th': 'ดาวดวงน้อยเปล่งแสงวิบวับ...', 'en': 'The little star twinkled brightly...'}, {'th': 'มนุษย์ต่างดาวที่เป็นมิตร...', 'en': 'The friendly alien...'} ], 'Intermediate': [ {'th': 'นักบินอวกาศพบสิ่งประหลาด...', 'en': 'The astronaut found something strange...'}, {'th': 'ดาวเคราะห์ดวงใหม่ถูกค้นพบ...', 'en': 'A new planet was discovered...'}, {'th': 'สถานีอวกาศส่งสัญญาณลึกลับ...', 'en': 'The space station sent a mysterious signal...'} ], 'Advanced': [ {'th': 'การสำรวจดาวหางนำไปสู่การค้นพบ...', 'en': 'The comet exploration led to a discovery...'}, {'th': 'กาแล็กซี่ที่ไม่มีใครเคยเห็น...', 'en': 'An unknown galaxy appeared...'}, {'th': 'ความลับของหลุมดำ...', 'en': 'The secret of the black hole...'} ] }, 'background_color': '#E1F5FE', 'accent_color': '#0288D1' }, 'adventure': { 'id': 'adventure', 'icon': '🗺️', 'name_en': 'Adventure & Quest', 'name_th': 'การผจญภัยและการค้นหา', 'description_th': 'ออกผจญภัยค้นหาสมบัติและความลับต่างๆ', 'description_en': 'Embark on quests to find treasures and secrets', 'level_range': ['Beginner', 'Intermediate', 'Advanced'], 'vocabulary': { 'Beginner': ['map', 'treasure', 'cave', 'island', 'path', 'boat', 'key', 'chest'], 'Intermediate': ['compass', 'adventure', 'journey', 'mystery', 'explore', 'discover', 'quest'], 'Advanced': ['expedition', 'archaeology', 'artifact', 'ancient', 'mysterious', 'discovery'] }, 'story_starters': { 'Beginner': [ {'th': 'แผนที่เก่าแก่ชิ้นหนึ่ง...', 'en': 'An old map showed...'}, {'th': 'บนเกาะเล็กๆ มีสมบัติ...', 'en': 'On a small island, there was a treasure...'}, {'th': 'ถ้ำลึกลับถูกค้นพบ...', 'en': 'A mysterious cave was found...'} ], 'Intermediate': [ {'th': 'เข็มทิศวิเศษชี้ไปที่...', 'en': 'The magical compass pointed to...'}, {'th': 'การเดินทางเริ่มต้นที่...', 'en': 'The journey began at...'}, {'th': 'ความลับของวัตถุโบราณ...', 'en': 'The secret of the ancient artifact...'} ], 'Advanced': [ {'th': 'การสำรวจซากปรักหักพังนำไปสู่...', 'en': 'The ruins exploration led to...'}, {'th': 'นักโบราณคดีค้นพบ...', 'en': 'The archaeologist discovered...'}, {'th': 'ความลับของอารยธรรมโบราณ...', 'en': 'The secret of the ancient civilization...'} ] }, 'background_color': '#FFF3E0', 'accent_color': '#FF9800' }, 'school': { 'id': 'school', 'icon': '🏫', 'name_en': 'School & Friends', 'name_th': 'โรงเรียนและเพื่อน', 'description_th': 'เรื่องราวสนุกๆ ในโรงเรียนกับเพื่อนๆ', 'description_en': 'Fun stories about school life and friendship', 'level_range': ['Beginner', 'Intermediate', 'Advanced'], 'vocabulary': { 'Beginner': ['friend', 'teacher', 'book', 'classroom', 'pencil', 'desk', 'lunch', 'play'], 'Intermediate': ['homework', 'project', 'library', 'playground', 'student', 'lesson', 'study'], 'Advanced': ['presentation', 'experiment', 'knowledge', 'research', 'collaboration', 'achievement'] }, 'story_starters': { 'Beginner': [ {'th': 'วันแรกในห้องเรียนใหม่...', 'en': 'First day in the new classroom...'}, {'th': 'เพื่อนใหม่ในโรงเรียน...', 'en': 'A new friend at school...'}, {'th': 'ที่โต๊ะอาหารกลางวัน...', 'en': 'At the lunch table...'} ], 'Intermediate': [ {'th': 'โครงงานพิเศษของห้องเรา...', 'en': 'Our class special project...'}, {'th': 'ในห้องสมุดมีความลับ...', 'en': 'The library had a secret...'}, {'th': 'การทดลองวิทยาศาสตร์ครั้งนี้...', 'en': 'This science experiment...'} ], 'Advanced': [ {'th': 'การนำเสนอครั้งสำคัญ...', 'en': 'The important presentation...'}, {'th': 'การค้นคว้าพิเศษนำไปสู่...', 'en': 'The special research led to...'}, {'th': 'โครงการความร่วมมือระหว่างห้อง...', 'en': 'The inter-class collaboration project...'} ] }, 'background_color': '#F3E5F5', 'accent_color': '#9C27B0' }, 'superhero': { 'id': 'superhero', 'icon': '🦸', 'name_en': 'Superheroes', 'name_th': 'ซูเปอร์ฮีโร่', 'description_th': 'เรื่องราวของฮีโร่ตัวน้อยผู้ช่วยเหลือผู้อื่น', 'description_en': 'Stories of young heroes helping others', 'level_range': ['Beginner', 'Intermediate', 'Advanced'], 'vocabulary': { 'Beginner': ['hero', 'help', 'save', 'power', 'mask', 'cape', 'fly', 'strong'], 'Intermediate': ['rescue', 'protect', 'brave', 'courage', 'mission', 'team', 'secret', 'mighty'], 'Advanced': ['superhero', 'extraordinary', 'responsibility', 'leadership', 'determination', 'justice'] }, 'story_starters': { 'Beginner': [ {'th': 'ฮีโร่น้อยคนใหม่ของเมือง...', 'en': 'The city\'s new young hero...'}, {'th': 'พลังพิเศษของฉันทำให้...', 'en': 'My special power made me...'}, {'th': 'เมื่อต้องช่วยเหลือแมวตัวน้อย...', 'en': 'When I had to save a little cat...'} ], 'Intermediate': [ {'th': 'ภารกิจลับของทีมฮีโร่...', 'en': 'The hero team\'s secret mission...'}, {'th': 'พลังใหม่ที่น่าประหลาดใจ...', 'en': 'A surprising new power...'}, {'th': 'การช่วยเหลือครั้งสำคัญ...', 'en': 'An important rescue mission...'} ], 'Advanced': [ {'th': 'ความรับผิดชอบของการเป็นฮีโร่...', 'en': 'The responsibility of being a hero...'}, {'th': 'เมื่อเมืองต้องการฮีโร่...', 'en': 'When the city needed a hero...'}, {'th': 'การต่อสู้เพื่อความยุติธรรม...', 'en': 'Fighting for justice...'} ] }, 'background_color': '#FFE0B2', 'accent_color': '#F57C00' }, 'mystery': { 'id': 'mystery', 'icon': '🔍', 'name_en': 'Mystery & Detective', 'name_th': 'ไขปริศนาและนักสืบ', 'description_th': 'สืบสวนปริศนาและไขความลับต่างๆ', 'description_en': 'Solve mysteries and uncover secrets', 'level_range': ['Beginner', 'Intermediate', 'Advanced'], 'vocabulary': { 'Beginner': ['clue', 'find', 'look', 'search', 'mystery', 'hidden', 'secret', 'detective'], 'Intermediate': ['investigate', 'evidence', 'puzzle', 'solve', 'discover', 'suspicious', 'case'], 'Advanced': ['investigation', 'deduction', 'enigma', 'cryptic', 'mysterious', 'revelation'] }, 'story_starters': { 'Beginner': [ {'th': 'มีรอยปริศนาในสวน...', 'en': 'There were mysterious footprints in the garden...'}, {'th': 'จดหมายลึกลับถูกส่งมา...', 'en': 'A mysterious letter arrived...'}, {'th': 'ของเล่นหายไปอย่างลึกลับ...', 'en': 'The toy mysteriously disappeared...'} ], 'Intermediate': [ {'th': 'เบาะแสชิ้นแรกนำไปสู่...', 'en': 'The first clue led to...'}, {'th': 'ความลับในห้องเก่า...', 'en': 'The secret in the old room...'}, {'th': 'รหัสลับถูกค้นพบ...', 'en': 'A secret code was found...'} ], 'Advanced': [ {'th': 'คดีปริศนาที่ยากที่สุด...', 'en': 'The most challenging mystery case...'}, {'th': 'ความลับที่ซ่อนอยู่มานาน...', 'en': 'A long-hidden secret...'}, {'th': 'การสืบสวนนำไปสู่การค้นพบ...', 'en': 'The investigation led to a discovery...'} ] }, 'background_color': '#E0E0E0', 'accent_color': '#616161' }, 'science': { 'id': 'science', 'icon': '🔬', 'name_en': 'Science & Discovery', 'name_th': 'วิทยาศาสตร์และการค้นพบ', 'description_th': 'การทดลองและค้นพบทางวิทยาศาสตร์ที่น่าตื่นเต้น', 'description_en': 'Exciting scientific experiments and discoveries', 'level_range': ['Beginner', 'Intermediate', 'Advanced'], 'vocabulary': { 'Beginner': ['experiment', 'science', 'lab', 'test', 'mix', 'observe', 'change', 'result'], 'Intermediate': ['hypothesis', 'research', 'discovery', 'invention', 'laboratory', 'scientist'], 'Advanced': ['innovation', 'technological', 'breakthrough', 'analysis', 'investigation'] }, 'story_starters': { 'Beginner': [ {'th': 'การทดลองง่ายๆ เริ่มต้นด้วย...', 'en': 'The simple experiment started with...'}, {'th': 'ในห้องทดลองมีสิ่งมหัศจรรย์...', 'en': 'In the lab, there was something amazing...'}, {'th': 'เมื่อผสมสองสิ่งเข้าด้วยกัน...', 'en': 'When mixing the two things together...'} ], 'Intermediate': [ {'th': 'การค้นพบที่น่าประหลาดใจ...', 'en': 'A surprising discovery...'}, {'th': 'สิ่งประดิษฐ์ใหม่ทำให้...', 'en': 'The new invention made...'}, {'th': 'การทดลองที่ไม่คาดคิด...', 'en': 'An unexpected experiment...'} ], 'Advanced': [ {'th': 'นวัตกรรมที่จะเปลี่ยนโลก...', 'en': 'Innovation that would change the world...'}, {'th': 'การค้นพบทางวิทยาศาสตร์ครั้งสำคัญ...', 'en': 'An important scientific discovery...'}, {'th': 'เทคโนโลยีใหม่ที่น่าทึ่ง...', 'en': 'Amazing new technology...'} ] }, 'background_color': '#E8EAF6', 'accent_color': '#3F51B5' } } # Set up logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) # === 2. CONFIGURATIONS === # Page Config st.set_page_config( page_title="JoyStory - Interactive Story Adventure", page_icon="📖", layout="wide", initial_sidebar_state="collapsed" ) # Initialize OpenAI client try: client = OpenAI() except Exception as e: logging.error(f"Failed to initialize OpenAI client: {str(e)}") st.error("Failed to initialize AI services. Please try again later.") # Define Constants MAX_RETRIES = 3 DEFAULT_LEVEL = 'Beginner' SUPPORTED_LANGUAGES = ['th', 'en'] # Achievement Requirements ACHIEVEMENT_THRESHOLDS = { 'perfect_writer': 5, # 5 correct sentences in a row 'vocabulary_master': 50, # 50 unique words used 'story_master': 10, # 10 sentences in story 'accuracy_king': 80 # 80% accuracy rate } # Level Configuration level_options = { 'Beginner': { 'thai_name': 'ระดับเริ่มต้น (ป.1-3)', 'age_range': '7-9 ปี', 'description': 'เหมาะสำหรับน้องๆ ที่เริ่มเรียนรู้การเขียนประโยคภาษาอังกฤษ', 'features': [ 'ประโยคสั้นๆ ง่ายๆ', 'คำศัพท์พื้นฐานที่ใช้ในชีวิตประจำวัน', 'มีคำแนะนำภาษาไทยละเอียด', 'เน้นการใช้ Present Simple Tense' ], 'max_sentence_length': 10, 'allowed_tenses': ['present_simple'], 'feedback_level': 'detailed' }, 'Intermediate': { 'thai_name': 'ระดับกลาง (ป.4-6)', 'age_range': '10-12 ปี', 'description': 'เหมาะสำหรับน้องๆ ที่สามารถเขียนประโยคพื้นฐานได้แล้ว', 'features': [ 'ประโยคซับซ้อนขึ้น', 'เริ่มใช้ Past Tense ได้', 'คำศัพท์หลากหลายขึ้น', 'สามารถเขียนเรื่องราวต่อเนื่องได้' ], 'max_sentence_length': 15, 'allowed_tenses': ['present_simple', 'past_simple'], 'feedback_level': 'moderate' }, 'Advanced': { 'thai_name': 'ระดับก้าวหน้า (ม.1-3)', 'age_range': '13-15 ปี', 'description': 'เหมาะสำหรับน้องๆ ที่มีพื้นฐานภาษาอังกฤษดี', 'features': [ 'เขียนเรื่องราวได้หลากหลายรูปแบบ', 'ใช้ Tense ต่างๆ ได้', 'คำศัพท์ระดับสูงขึ้น', 'สามารถแต่งเรื่องที่ซับซ้อนได้' ], 'max_sentence_length': 20, 'allowed_tenses': ['present_simple', 'past_simple', 'present_perfect', 'past_perfect'], 'feedback_level': 'concise' } } # Achievement Configuration achievements_list = { 'perfect_writer': { 'name': '🌟 นักเขียนไร้ที่ติ', 'description': 'เขียนถูกต้อง 5 ประโยคติดต่อกัน', 'condition': lambda: st.session_state.points['streak'] >= ACHIEVEMENT_THRESHOLDS['perfect_writer'] }, 'vocabulary_master': { 'name': '📚 ราชาคำศัพท์', 'description': 'ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ', 'condition': lambda: len(st.session_state.stats['vocabulary_used']) >= ACHIEVEMENT_THRESHOLDS['vocabulary_master'] }, 'story_master': { 'name': '📖 นักแต่งนิทาน', 'description': 'เขียนเรื่องยาว 10 ประโยค', 'condition': lambda: len(st.session_state.story) >= ACHIEVEMENT_THRESHOLDS['story_master'] }, 'accuracy_king': { 'name': '👑 ราชาความแม่นยำ', 'description': 'มีอัตราความถูกต้อง 80% ขึ้นไป (อย่างน้อย 10 ประโยค)', 'condition': lambda: ( st.session_state.stats['total_sentences'] >= 10 and st.session_state.stats['accuracy_rate'] >= ACHIEVEMENT_THRESHOLDS['accuracy_king'] ) } } # Initial CSS Setup st.markdown(""" """, unsafe_allow_html=True) # Reset counter when page reloads if 'theme_button_counter' in st.session_state: st.session_state.theme_button_counter = 0 # === 3. STATE MANAGEMENT === def init_session_state(): """Initialize all session state variables with default values""" # Basic states default_states = { 'theme_selection_id': datetime.now().strftime('%Y%m%d%H%M%S'), 'current_theme': None, 'theme_button_counter': 0, 'theme_story_starter': None, 'story': [], 'feedback': None, 'level': DEFAULT_LEVEL, 'should_reset': False, 'last_interaction': datetime.now().isoformat(), } # Points system points_states = { 'points': { 'total': 0, 'perfect_sentences': 0, 'corrections_made': 0, 'streak': 0, 'max_streak': 0 } } # Statistics tracking stats_states = { 'stats': { 'total_sentences': 0, 'correct_first_try': 0, 'accuracy_rate': 0.0, 'vocabulary_used': set(), 'corrections_made': 0, 'average_sentence_length': 0, 'total_words': 0, 'session_duration': 0 } } # Game progress progress_states = { 'achievements': [], 'unlocked_features': set(), 'current_milestone': 0, 'next_milestone': 5 } # User preferences preferences_states = { 'language': 'th', 'feedback_level': 'detailed', 'theme_color': 'light', 'sound_enabled': True } # Initialize all states if they don't exist for state_dict in [default_states, points_states, stats_states, progress_states, preferences_states]: for key, value in state_dict.items(): if key not in st.session_state: st.session_state[key] = value if 'clear_input' not in st.session_state: st.session_state.clear_input = False if 'text_input' not in st.session_state: st.session_state.text_input = "" def init_theme_state(): """Initialize theme-specific state variables""" if 'current_theme' not in st.session_state: st.session_state.current_theme = None if 'theme_story_starter' not in st.session_state: st.session_state.theme_story_starter = None def reset_story(): """Reset story and related state variables""" try: # Reset story-related states st.session_state.story = [] st.session_state.feedback = None st.session_state.theme_story_starter = None # Reset points st.session_state.points = { 'total': 0, 'perfect_sentences': 0, 'corrections_made': 0, 'streak': 0, 'max_streak': 0 } # Reset stats st.session_state.stats = { 'total_sentences': 0, 'correct_first_try': 0, 'accuracy_rate': 0.0, 'vocabulary_used': set(), 'corrections_made': 0, 'average_sentence_length': 0, 'total_words': 0, 'session_duration': 0 } # Reset progress st.session_state.current_milestone = 0 st.session_state.next_milestone = 5 # Reset flag st.session_state.should_reset = False # Log reset logging.info("Story state reset successfully") return True except Exception as e: logging.error(f"Error resetting story state: {str(e)}") st.error("เกิดข้อผิดพลาดในการรีเซ็ตเรื่อง กรุณาลองใหม่อีกครั้ง") return False def save_progress() -> Dict: """Save current progress to JSON format""" try: progress_data = { 'timestamp': datetime.now().isoformat(), 'level': st.session_state.level, 'story': st.session_state.story, 'stats': { key: list(value) if isinstance(value, set) else value for key, value in st.session_state.stats.items() }, 'points': st.session_state.points, 'achievements': st.session_state.achievements, 'current_theme': st.session_state.current_theme } logging.info("Progress saved successfully") return progress_data except Exception as e: logging.error(f"Error saving progress: {str(e)}") raise def load_progress(data: Dict): """Load progress from saved data""" try: # Validate data structure required_keys = ['level', 'story', 'stats', 'points', 'achievements'] if not all(key in data for key in required_keys): raise ValueError("Invalid save data format") # Load basic data st.session_state.level = data['level'] st.session_state.story = data['story'] st.session_state.achievements = data['achievements'] st.session_state.points = data['points'] # Load stats (converting lists back to sets where needed) st.session_state.stats = { 'total_sentences': data['stats']['total_sentences'], 'correct_first_try': data['stats']['correct_first_try'], 'accuracy_rate': data['stats']['accuracy_rate'], 'vocabulary_used': set(data['stats']['vocabulary_used']), 'corrections_made': data['stats']['corrections_made'], 'average_sentence_length': data['stats'].get('average_sentence_length', 0), 'total_words': data['stats'].get('total_words', 0), 'session_duration': data['stats'].get('session_duration', 0) } # Load theme if present if 'current_theme' in data: st.session_state.current_theme = data['current_theme'] logging.info("Progress loaded successfully") st.success("โหลดความก้าวหน้าเรียบร้อย!") return True except Exception as e: logging.error(f"Error loading progress: {str(e)}") st.error("เกิดข้อผิดพลาดในการโหลดข้อมูล กรุณาตรวจสอบไฟล์และลองใหม่อีกครั้ง") return False def update_session_stats(): """Update session statistics""" try: if st.session_state.story: # Update word counts all_text = ' '.join([entry['content'] for entry in st.session_state.story]) words = all_text.split() st.session_state.stats['total_words'] = len(words) # Update average sentence length if st.session_state.stats['total_sentences'] > 0: st.session_state.stats['average_sentence_length'] = ( st.session_state.stats['total_words'] / st.session_state.stats['total_sentences'] ) # Update session duration start_time = datetime.fromisoformat(st.session_state.last_interaction) current_time = datetime.now() duration = (current_time - start_time).total_seconds() st.session_state.stats['session_duration'] = duration # Update last interaction time st.session_state.last_interaction = current_time.isoformat() return True except Exception as e: logging.error(f"Error updating session stats: {str(e)}") return False # === 4. UTILITY FUNCTIONS === def generate_story_continuation(user_input: str, level: str) -> str: """Generate AI story continuation using ChatGPT""" level_context = { 'Beginner': { 'instructions': """ Role: Teaching assistant for Thai students (grades 1-3) Rules: - Use only 1-2 VERY simple sentences - Use Present Simple Tense only - Basic vocabulary (family, school, daily activities) - 5-7 words per sentence maximum - Focus on clear, basic responses """, 'max_tokens': 30, 'temperature': 0.6 }, 'Intermediate': { 'instructions': """ Role: Teaching assistant for Thai students (grades 4-6) Rules: - Use exactly 2 sentences maximum - Can use Present or Past Tense - Keep each sentence under 12 words - Grade-appropriate vocabulary - Add simple descriptions but stay concise """, 'max_tokens': 40, 'temperature': 0.7 }, 'Advanced': { 'instructions': """ Role: Teaching assistant for Thai students (grades 7-9) Rules: - Use 2-3 sentences maximum - Various tenses allowed - Natural sentence length but keep overall response concise - More sophisticated vocabulary and structures - Create engaging responses that encourage creative continuation """, 'max_tokens': 50, 'temperature': 0.8 } } try: # Get recent story context story_context = '\n'.join([ entry['content'] for entry in st.session_state.story[-3:] ]) if st.session_state.story else "Story just started" # Create prompt level_settings = level_context[level] for _ in range(MAX_RETRIES): try: response = client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "system", "content": f""" {level_settings['instructions']} CRUCIAL GUIDELINES: - Never exceed the maximum sentences for the level - Create openings for student's creativity - Do not resolve plot points or conclude the story - Avoid using 'suddenly' or 'then' - Make each sentence meaningful but incomplete - Leave room for the student to develop the story """ }, { "role": "user", "content": f"Story context:\n{story_context}\nStudent's input:\n{user_input}\nProvide a brief continuation:" } ], max_tokens=level_settings['max_tokens'], temperature=level_settings['temperature'], presence_penalty=0.6, frequency_penalty=0.6 ) # Process and clean response response_text = response.choices[0].message.content.strip() sentences = [s.strip() for s in response_text.split('.') if s.strip()] # Limit sentences based on level max_sentences = {'Beginner': 2, 'Intermediate': 2, 'Advanced': 3} if len(sentences) > max_sentences[level]: sentences = sentences[:max_sentences[level]] # Reconstruct response final_response = '. '.join(sentences) + '.' logging.info(f"Generated continuation for level {level}") return final_response except Exception as e: if _ < MAX_RETRIES - 1: logging.warning(f"Retry {_+1} failed: {str(e)}") continue raise raise Exception("Max retries exceeded") except Exception as e: logging.error(f"Error generating story continuation: {str(e)}") return "I'm having trouble continuing the story. Please try again." def generate_dynamic_story_starter(theme_id: str, level: str) -> Dict[str, str]: """ Dynamically generate a story starter based on theme and level. Returns both Thai and English versions. """ try: # Get theme data theme = story_themes.get(theme_id) if not theme: raise ValueError(f"Theme {theme_id} not found") # Get random starter for the level level_starters = theme['story_starters'].get(level, []) if not level_starters: # Fallback to Beginner level if no starters found for specified level level_starters = theme['story_starters'].get('Beginner', []) if not level_starters: raise ValueError(f"No story starters found for theme {theme_id}") # Select random starter starter = random.choice(level_starters) # Return both languages return { 'en': starter['en'], 'th': starter['th'] } except Exception as e: logging.error(f"Error generating story starter: {str(e)}") # Provide fallback starter return { 'en': 'Once upon a time...', 'th': 'กาลครั้งหนึ่ง...' } def update_points(is_correct_first_try: bool): """อัพเดตคะแนนตามผลการเขียน""" try: # คำนวณคะแนนพื้นฐาน base_points = 10 if is_correct_first_try: # ถูกต้องในครั้งแรก points = base_points * 2 st.session_state.points['perfect_sentences'] += 1 st.session_state.points['streak'] += 1 # อัพเดต max streak if st.session_state.points['streak'] > st.session_state.points['max_streak']: st.session_state.points['max_streak'] = st.session_state.points['streak'] else: # ต้องแก้ไข points = base_points // 2 st.session_state.points['corrections_made'] += 1 st.session_state.points['streak'] = 0 # เพิ่มคะแนนรวม st.session_state.points['total'] += points # อัพเดตสถิติ st.session_state.stats['total_sentences'] += 1 if is_correct_first_try: st.session_state.stats['correct_first_try'] += 1 # คำนวณอัตราความถูกต้อง st.session_state.stats['accuracy_rate'] = ( st.session_state.stats['correct_first_try'] / st.session_state.stats['total_sentences'] * 100 ) logging.info(f"Points updated: +{points} points") return True except Exception as e: logging.error(f"Error updating points: {str(e)}") return False def apply_correction(story_index: int, corrected_text: str): """แก้ไขประโยคในเรื่อง""" try: if not (0 <= story_index < len(st.session_state.story)): raise ValueError("Invalid story index") # เก็บข้อความเดิม original_text = st.session_state.story[story_index]['content'] # อัพเดทข้อความ st.session_state.story[story_index].update({ 'content': corrected_text, 'is_corrected': True, 'is_correct': True, 'original_text': original_text, 'correction_timestamp': datetime.now().isoformat() }) # อัพเดทสถิติ st.session_state.stats['corrections_made'] += 1 # Log การแก้ไข logging.info(f"Sentence corrected at index {story_index}") # แสดงข้อความยืนยัน st.success("✅ แก้ไขประโยคเรียบร้อยแล้ว!") return True except Exception as e: logging.error(f"Error applying correction: {str(e)}") st.error("เกิดข้อผิดพลาดในการแก้ไขประโยค กรุณาลองใหม่อีกครั้ง") return False def update_achievements(): """ตรวจสอบและอัพเดตความสำเร็จ""" try: current_achievements = st.session_state.achievements # ตรวจสอบเงื่อนไขต่างๆ if (st.session_state.points['streak'] >= 5 and "🌟 นักเขียนไร้ที่ติ" not in current_achievements): current_achievements.append("🌟 นักเขียนไร้ที่ติ") st.success("🎉 ได้รับความสำเร็จใหม่: นักเขียนไร้ที่ติ!") if (len(st.session_state.stats['vocabulary_used']) >= 50 and "📚 ราชาคำศัพท์" not in current_achievements): current_achievements.append("📚 ราชาคำศัพท์") st.success("🎉 ได้รับความสำเร็จใหม่: ราชาคำศัพท์!") if (len(st.session_state.story) >= 10 and "📖 นักแต่งนิทาน" not in current_achievements): current_achievements.append("📖 นักแต่งนิทาน") st.success("🎉 ได้รับความสำเร็จใหม่: นักแต่งนิทาน!") if (st.session_state.stats['total_sentences'] >= 10 and st.session_state.stats['accuracy_rate'] >= 80 and "👑 ราชาความแม่นยำ" not in current_achievements): current_achievements.append("👑 ราชาความแม่นยำ") st.success("🎉 ได้รับความสำเร็จใหม่: ราชาความแม่นยำ!") # บันทึกความสำเร็จ st.session_state.achievements = current_achievements logging.info("Achievements updated successfully") return True except Exception as e: logging.error(f"Error updating achievements: {str(e)}") return False def provide_feedback(text: str, level: str) -> Dict[str, str]: """Provide feedback on the user's writing with appropriate level""" level_prompts = { 'Beginner': """ Focus on: - Basic sentence structure (Subject + Verb + Object) - Present Simple Tense usage - Basic vocabulary - Capitalization and periods Provide very simple, encouraging feedback in Thai. """, 'Intermediate': """ Focus on: - Sentence variety - Past Tense usage - Vocabulary appropriateness - Basic punctuation - Simple conjunctions Provide moderately detailed feedback in Thai. """, 'Advanced': """ Focus on: - Complex sentence structures - Various tense usage - Advanced vocabulary - All punctuation - Style and flow Provide comprehensive feedback in Thai. """ } try: for _ in range(MAX_RETRIES): try: response = client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "system", "content": f""" You are a Thai English teacher helping {level} students. {level_prompts[level]} Return your response in this EXACT format (valid JSON): {{ "feedback": "(ข้อเสนอแนะภาษาไทย)", "corrected": "(ประโยคภาษาอังกฤษที่ถูกต้อง)", "has_errors": true/false, "error_types": ["grammar", "vocabulary", "spelling", ...], "difficulty_score": 1-10 }} """ }, { "role": "user", "content": f"Review this sentence: {text}" } ], max_tokens=200, temperature=0.3 ) # Parse response feedback_data = json.loads(response.choices[0].message.content.strip()) # Validate required fields required_fields = ['feedback', 'corrected', 'has_errors'] if not all(field in feedback_data for field in required_fields): raise ValueError("Missing required fields in feedback") logging.info(f"Generated feedback for level {level}") return feedback_data except json.JSONDecodeError: if _ < MAX_RETRIES - 1: continue raise raise Exception("Max retries exceeded") except Exception as e: logging.error(f"Error generating feedback: {str(e)}") return { "feedback": "⚠️ ระบบไม่สามารถวิเคราะห์ประโยคได้ กรุณาลองใหม่อีกครั้ง", "corrected": text, "has_errors": False, "error_types": [], "difficulty_score": 5 } def get_vocabulary_suggestions(context: str = "", level: str = DEFAULT_LEVEL) -> List[str]: """Get contextual vocabulary suggestions with Thai translations""" try: recent_story = context or '\n'.join([ entry['content'] for entry in st.session_state.story[-3:] ]) if st.session_state.story else "Story just started" response = client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "system", "content": f""" You are a Thai-English bilingual teacher. Suggest 5 English words appropriate for {level} level students. Format each suggestion as: word (type) - คำแปล | example sentence """ }, { "role": "user", "content": f"Story context:\n{recent_story}\n\nSuggest relevant words:" } ], max_tokens=200, temperature=0.8 ) suggestions = response.choices[0].message.content.split('\n') return [s.strip() for s in suggestions if s.strip()] except Exception as e: logging.error(f"Error getting vocabulary suggestions: {str(e)}") return [ "happy (adj) - มีความสุข | I am happy today", "run (verb) - วิ่ง | The dog runs fast", "tree (noun) - ต้นไม้ | A tall tree" ] def get_creative_prompt() -> Dict[str, str]: """Generate a bilingual creative writing prompt""" try: response = client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "system", "content": """ Create short story prompts in both English and Thai. Keep it simple and under 6 words each. Make it open-ended and encouraging. """ }, { "role": "user", "content": "Generate a creative writing prompt:" } ], max_tokens=50, temperature=0.7 ) prompt_eng = response.choices[0].message.content.strip() # Get Thai translation response_thai = client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "system", "content": "Translate to short Thai prompt, keep it natural:" }, { "role": "user", "content": prompt_eng } ], max_tokens=50, temperature=0.7 ) prompt_thai = response_thai.choices[0].message.content.strip() return { "eng": prompt_eng, "thai": prompt_thai } except Exception as e: logging.error(f"Error generating creative prompt: {str(e)}") return { "eng": "What happens next?", "thai": "แล้วอะไรจะเกิดขึ้นต่อ?" } def calculate_text_metrics(text: str) -> Dict[str, float]: """Calculate various metrics for the given text""" try: words = text.split() sentences = [s.strip() for s in text.split('.') if s.strip()] metrics = { 'word_count': len(words), 'sentence_count': len(sentences), 'average_word_length': sum(len(word) for word in words) / len(words) if words else 0, 'unique_words': len(set(words)), 'complexity_score': calculate_complexity_score(text) } return metrics except Exception as e: logging.error(f"Error calculating text metrics: {str(e)}") return { 'word_count': 0, 'sentence_count': 0, 'average_word_length': 0, 'unique_words': 0, 'complexity_score': 0 } def calculate_complexity_score(text: str) -> float: """Calculate a complexity score for the text (0-10)""" try: # Basic metrics words = text.split() word_count = len(words) unique_words = len(set(words)) avg_word_length = sum(len(word) for word in words) / word_count if word_count > 0 else 0 # Calculate score components vocabulary_score = (unique_words / word_count) * 5 if word_count > 0 else 0 length_score = min((avg_word_length / 10) * 5, 5) # Combine scores total_score = vocabulary_score + length_score return min(total_score, 10.0) except Exception as e: logging.error(f"Error calculating complexity score: {str(e)}") return 5.0 # === 5. UI COMPONENTS === def show_welcome_section(): """Display welcome message and introduction""" st.markdown("""
💡 เคล็ดลับ: ให้น้องๆ พูดเรื่องราวที่อยากเขียนเป็นภาษาไทยก่อน แล้วค่อยๆ ช่วยกันแปลงเป็นภาษาอังกฤษ
🎯 {level_options[level]['description']}
👥 เหมาะสำหรับอายุ: {level_options[level]['age_range']}
เลือกโลกแห่งจินตนาการที่คุณต้องการผจญภัย และเริ่มต้นเขียนเรื่องราวของคุณ
{story_summary}
จำนวนประโยค: {len(st.session_state.story)}
คำศัพท์ที่ใช้: {len(st.session_state.stats['vocabulary_used'])}
ความแม่นยำ: {st.session_state.stats['accuracy_rate']:.1f}%
{feedback_data['feedback']}
ประโยคที่ถูกต้อง:
{feedback_data['corrected']}
{feedback_data.get('feedback', '✨ เขียนได้ถูกต้องแล้วค่ะ!')}
🔥 Streak ปัจจุบัน: {st.session_state.points['streak']} ประโยค
⭐ Streak สูงสุด: {st.session_state.points['max_streak']} ประโยค
รางวัลที่รอคุณอยู่:
กรุณาลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบ