diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,337 +1,54 @@ +# === 1. IMPORTS & SETUP === import streamlit as st import json import datetime +import logging from openai import OpenAI -from typing import Dict, List, Set +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.lib.units import inch -from reportlab.pdfgen import canvas -from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image -from reportlab.pdfbase import pdfmetrics -from reportlab.pdfbase.ttfonts import TTFont +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer from datetime import datetime -from typing import Dict, List, Optional, Tuple import random -# Reset counter when page reloads -if 'theme_button_counter' in st.session_state: - st.session_state.theme_button_counter = 0 - -# 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' - } -} - -# ระบบ Achievements -achievements_list = { - 'perfect_writer': { - 'name': '🌟 นักเขียนไร้ที่ติ', - 'description': 'เขียนถูกต้อง 5 ประโยคติดต่อกัน', - 'condition': lambda: st.session_state.points['streak'] >= 5 - }, - 'vocabulary_master': { - 'name': '📚 ราชาคำศัพท์', - 'description': 'ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ', - 'condition': lambda: len(st.session_state.stats['vocabulary_used']) >= 50 - }, - 'quick_learner': { - 'name': '🚀 นักเรียนจอมขยัน', - 'description': 'แก้ไขประโยคให้ถูกต้องภายใน 3 วินาที', - 'condition': lambda: True # ต้องเพิ่มการจับเวลา - }, - 'story_master': { - 'name': '📖 นักแต่งนิทาน', - 'description': 'เขียนเรื่องยาว 10 ประโยค', - 'condition': lambda: len(st.session_state.story) >= 10 - }, - 'accuracy_king': { - 'name': '👑 ราชาความแม่นยำ', - 'description': 'มีอัตราความถูกต้อง 80% ขึ้นไป (อย่างน้อย 10 ประโยค)', - 'condition': lambda: ( - st.session_state.stats['total_sentences'] >= 10 and - st.session_state.stats['accuracy_rate'] >= 80 - ) - } -} +# Set up logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) -# Set up Streamlit page configuration +# === 2. CONFIGURATIONS === +# Page Config st.set_page_config( page_title="JoyStory - Interactive Story Adventure", page_icon="📖", layout="wide", - initial_sidebar_state="collapsed", + initial_sidebar_state="collapsed" ) # Initialize OpenAI client -client = OpenAI() +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 +} -# Define level configurations +# Level Configuration level_options = { 'Beginner': { 'thai_name': 'ระดับเริ่มต้น (ป.1-3)', @@ -342,7 +59,10 @@ level_options = { 'คำศัพท์พื้นฐานที่ใช้ในชีวิตประจำวัน', 'มีคำแนะนำภาษาไทยละเอียด', 'เน้นการใช้ Present Simple Tense' - ] + ], + 'max_sentence_length': 10, + 'allowed_tenses': ['present_simple'], + 'feedback_level': 'detailed' }, 'Intermediate': { 'thai_name': 'ระดับกลาง (ป.4-6)', @@ -353,7 +73,10 @@ level_options = { 'เริ่มใช้ Past Tense ได้', 'คำศัพท์หลากหลายขึ้น', 'สามารถเขียนเรื่องราวต่อเนื่องได้' - ] + ], + 'max_sentence_length': 15, + 'allowed_tenses': ['present_simple', 'past_simple'], + 'feedback_level': 'moderate' }, 'Advanced': { 'thai_name': 'ระดับก้าวหน้า (ม.1-3)', @@ -364,67 +87,51 @@ level_options = { 'ใช้ Tense ต่างๆ ได้', 'คำศัพท์ระดับสูงขึ้น', 'สามารถแต่งเรื่องที่ซับซ้อนได้' - ] + ], + 'max_sentence_length': 20, + 'allowed_tenses': ['present_simple', 'past_simple', 'present_perfect', 'past_perfect'], + 'feedback_level': 'concise' } } -# Add custom CSS including new styles for level selection -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 + } } - .theme-card:before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(45deg, rgba(255,255,255,0.1), rgba(255,255,255,0.5)); - opacity: 0; - transition: opacity 0.3s ease; + # 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 + } } - .theme-card:hover:before { - opacity: 1; + # Game progress + progress_states = { + 'achievements': [], + 'unlocked_features': set(), + 'current_milestone': 0, + 'next_milestone': 5 } - .theme-card.selected { - transform: scale(0.98); - box-shadow: 0 2px 5px rgba(0,0,0,0.2); - } - - /* CSS สำหรับปุ่ม Change Theme */ - .stButton button { - padding: 0.5rem 1rem; - border-radius: 0.5rem; - border: 1px solid rgba(49, 51, 63, 0.2); - background-color: #ffffff; - color: #31333f; - transition: all 0.2s ease; + # User preferences + preferences_states = { + 'language': 'th', + 'feedback_level': 'detailed', + 'theme_color': 'light', + 'sound_enabled': True } - .stButton button:hover { - border-color: #1e88e5; - color: #1e88e5; - transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0,0,0,0.1); - } - - /* ปรับแต่ง tooltip */ - .stTooltipIcon { - color: #1e88e5; - } - - /* Responsive Design */ - @media (max-width: 768px) { - .theme-card { - min-height: 120px; - } - - .theme-card h4 { - font-size: 0.9em; - } - - .theme-card p { - font-size: 0.8em; - } - } - - /* Story Display Styling */ - .story-message { - padding: 10px; - margin: 5px 0; - border-radius: 5px; - } - .story-message.ai { - background-color: #f0f7ff; - } - .story-message.user { - background-color: #fff; - } - .story-message.corrected { - background-color: #f0f9ff; - } - -""", unsafe_allow_html=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 -# Initialize session state variables -def init_session_state(): - if 'theme_selection_id' not in st.session_state: - st.session_state.theme_selection_id = datetime.now().strftime('%Y%m%d%H%M%S') +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_button_counter' not in st.session_state: - st.session_state.theme_button_counter = 0 if 'theme_story_starter' not in st.session_state: st.session_state.theme_story_starter = None - if 'story' not in st.session_state: + +def reset_story(): + """Reset story and related state variables""" + try: + # Reset story-related states st.session_state.story = [] - if 'feedback' not in st.session_state: st.session_state.feedback = None - if 'level' not in st.session_state: - st.session_state.level = 'Beginner' - if 'unique_words' not in st.session_state: - st.session_state.unique_words = set() - if 'total_words' not in st.session_state: - st.session_state.total_words = 0 - if 'should_reset' not in st.session_state: - st.session_state.should_reset = False - if 'user_input' not in st.session_state: - st.session_state.user_input = "" - if 'points' not in st.session_state: + st.session_state.theme_story_starter = None + + # Reset points st.session_state.points = { 'total': 0, 'perfect_sentences': 0, @@ -583,1814 +259,674 @@ def init_session_state(): 'streak': 0, 'max_streak': 0 } - if 'corrections' not in st.session_state: - st.session_state.corrections = {} - if 'stats' not in st.session_state: + + # Reset stats st.session_state.stats = { 'total_sentences': 0, 'correct_first_try': 0, 'accuracy_rate': 0.0, 'vocabulary_used': set(), - 'corrections_made': 0 + 'corrections_made': 0, + 'average_sentence_length': 0, + 'total_words': 0, + 'session_duration': 0 } - if 'achievements' not in st.session_state: - st.session_state.achievements = [] - -init_session_state() + + # 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 -# เพิ่มฟังก์ชันสำหรับจัดการ input -def clear_input(): - st.session_state.user_input = "" +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 -# Callback function for submit button -def submit_story(): - """Handle story submission and processing.""" - if st.session_state.text_input.strip(): - user_text = st.session_state.text_input.strip() +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("โหลดความก้าวหน้าเรียบร้อย!") - # ตรวจสอบว่ามีเรื่องเริ่มต้นหรือยัง - if not st.session_state.story: - st.error("กรุณาเลือกธีมเรื่องราวก่อนเริ่มเขียน") - return + return True + + except Exception as e: + logging.error(f"Error loading progress: {str(e)}") + st.error("เกิดข้อผิดพลาดในการโหลดข้อมูล กรุณาตรวจสอบไฟล์และลองใหม่อีกครั้ง") + return False - try: - # รับ feedback และตรวจสอบความถูกต้อง - feedback_data = provide_feedback(user_text, st.session_state.level) - st.session_state.feedback = feedback_data - is_correct = not feedback_data.get('has_errors', False) - - # เพิ่มประโยคของผู้ใช้ (เพิ่มครั้งเดียว) - st.session_state.story.append({ - "role": "You", - "content": user_text, - "is_corrected": False, - "is_correct": is_correct - }) - - # เพิ่มคำศัพท์ที่ใช้ - words = set(user_text.lower().split()) - st.session_state.stats['vocabulary_used'].update(words) - - # อัพเดตคะแนนและ achievements - update_points(is_correct) - update_achievements() +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) - # Generate AI continuation - text_for_continuation = feedback_data['corrected'] if feedback_data['has_errors'] else user_text - ai_response = generate_story_continuation(text_for_continuation, st.session_state.level) + # 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'] + ) - # เพิ่มการตอบกลับของ AI - st.session_state.story.append({ - "role": "AI", - "content": ai_response - }) + # 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 - # Clear input - st.session_state.text_input = "" + # Update last interaction time + st.session_state.last_interaction = current_time.isoformat() - except Exception as e: - st.error("เกิดข้อผิดพลาดในการวิเคราะห์ประโยค กรุณาลองใหม่อีกครั้ง") - st.error(f"Debug - Error in submit_story: {str(e)}") - - -# Theme Helper Functions -def get_available_themes(level: str) -> List[Dict]: - """Get list of themes available for the current level.""" - return [ - theme for theme in story_themes.values() - if level in theme['level_range'] - ] - -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. - """ - theme = story_themes.get(theme_id, {}) + return True - # Theme-specific elements for dynamic generation - theme_elements = { - 'fantasy': { - 'characters': { - 'Beginner': [ - ('young wizard', 'พ่อมดน้อย'), - ('fairy', 'นางฟ้า'), - ('friendly dragon', 'มังกรใจดี'), - ('magical cat', 'แมวเวทมนตร์'), - ], - 'Intermediate': [ - ('mysterious wizard', 'พ่อมดลึกลับ'), - ('ancient dragon', 'มังกรโบราณ'), - ('enchanted princess', 'เจ้าหญิงต้องมนตร์'), - ], - 'Advanced': [ - ('legendary sorcerer', 'จอมเวทในตำนาน'), - ('mythical creature', 'สัตว์ในตำนาน'), - ('wise elder wizard', 'พ่อมดผู้เฒ่าผู้เป็นปราชญ์'), - ] - }, - 'locations': { - 'Beginner': [ - ('magical garden', 'สวนเวทมนตร์'), - ('enchanted forest', 'ป่าวิเศษ'), - ], - 'Intermediate': [ - ('ancient castle', 'ปราสาทโบราณ'), - ('wizard school', 'โรงเรียนเวทมนตร์'), - ], - 'Advanced': [ - ('mystical realm', 'อาณาจักรเวทมนตร์'), - ('floating islands', 'เกาะลอยฟ้า'), - ] - }, - 'objects': { - 'Beginner': [ - ('magic wand', 'ไม้กายสิทธิ์'), - ('glowing crystal', 'คริสตัลเรืองแสง'), - ], - 'Intermediate': [ - ('ancient spellbook', 'ตำราเวทโบราณ'), - ('magical artifact', 'วัตถุวิเศษ'), - ], - 'Advanced': [ - ('legendary sword', 'ดาบในตำนาน'), - ('mystical orb', 'ลูกแก้ววิเศษ'), - ] - } - }, - 'nature': { - 'characters': { - 'Beginner': [ - ('little bird', 'นกน้อย'), - ('friendly squirrel', 'กระรอกน้อยใจดี'), - ('playful rabbit', 'กระต่ายซน'), - ], - 'Intermediate': [ - ('wise owl', 'นกฮูกผู้รอบรู้'), - ('busy beaver', 'บีเวอร์ขยัน'), - ('clever fox', 'จิ้งจอกเจ้าปัญญา'), - ], - 'Advanced': [ - ('majestic eagle', 'อินทรีผู้สง่างาม'), - ('mysterious panther', 'เสือดำลึกลับ'), - ] - }, - 'locations': { - 'Beginner': [ - ('flower garden', 'สวนดอกไม้'), - ('small pond', 'สระน้ำเล็กๆ'), - ], - 'Intermediate': [ - ('deep forest', 'ป่าลึก'), - ('mountain peak', 'ยอดเขาสูง'), - ], - 'Advanced': [ - ('ancient rainforest', 'ป่าดึกดำบรรพ์'), - ('hidden valley', 'หุบเขาซ่อนเร้น'), - ] - }, - 'objects': { - 'Beginner': [ - ('colorful flower', 'ดอกไม้สีสวย'), - ('fallen leaf', 'ใบไม้ร่วง'), - ], - 'Intermediate': [ - ('old tree', 'ต้นไม้เก่าแก่'), - ('clear stream', 'ลำธารใส'), - ], - 'Advanced': [ - ('rare plant', 'พืชหายาก'), - ('ancient tree', 'ต้นไม้โบราณ'), - ] - } - }, - 'space': { - 'characters': { - 'Beginner': [ - ('friendly alien', 'มนุษย์ต่างดาวใจดี'), - ('little astronaut', 'นักบินอวกาศตัวน้อย'), - ('space robot', 'หุ่นยนต์อวกาศ'), - ], - 'Intermediate': [ - ('space explorer', 'นักสำรวจอวกาศ'), - ('alien scientist', 'นักวิทยาศาสตร์ต่างดาว'), - ('space pilot', 'นักบินยานอวกาศ'), - ], - 'Advanced': [ - ('galactic commander', 'ผู้บัญชาการกาแล็กซี่'), - ('space archaeologist', 'นักโบราณคดีอวกาศ'), - ] - }, - 'locations': { - 'Beginner': [ - ('small planet', 'ดาวเคราะห์เล็กๆ'), - ('space station', 'สถานีอวกาศ'), - ], - 'Intermediate': [ - ('mysterious galaxy', 'กาแล็กซี่ลึกลับ'), - ('alien world', 'โลกต่างดาว'), - ], - 'Advanced': [ - ('black hole', 'หลุมดำ'), - ('distant nebula', 'เนบิวลาไกลโพ้น'), - ] - }, - 'objects': { - 'Beginner': [ - ('shiny meteor', 'ดาวตกเปล่งประกาย'), - ('space telescope', 'กล้องดูดาว'), - ], - 'Intermediate': [ - ('advanced spaceship', 'ยานอวกาศลำใหม่'), - ('mysterious signal', 'สัญญาณลึกลับ'), - ], - 'Advanced': [ - ('alien artifact', 'วัตถุโบราณจากต่างดาว'), - ('space anomaly', 'ความผิดปกติในอวกาศ'), - ] - } - }, - 'adventure': { - 'characters': { - 'Beginner': [ - ('young explorer', 'นักผจญภัยตัวน้อย'), - ('treasure hunter', 'นักล่าสมบัติ'), - ('brave sailor', 'กะลาสีผู้กล้า'), - ], - 'Intermediate': [ - ('experienced guide', 'ไกด์ผู้ชำนาญ'), - ('mysterious traveler', 'นักเดินทางลึกลับ'), - ], - 'Advanced': [ - ('legendary explorer', 'นักสำรวจในตำนาน'), - ('ancient guardian', 'ผู้พิทักษ์โบราณ'), - ] - }, - 'locations': { - 'Beginner': [ - ('hidden cave', 'ถ้ำซ่อนเร้น'), - ('treasure island', 'เกาะสมบัติ'), - ], - 'Intermediate': [ - ('ancient ruins', 'ซากปรักหักพัง'), - ('mysterious temple', 'วัดลึกลับ'), - ], - 'Advanced': [ - ('lost city', 'เมืองที่สาบสูญ'), - ('forbidden valley', 'หุบเขาต้องห้าม'), - ] - }, - 'objects': { - 'Beginner': [ - ('old map', 'แผนที่เก่า'), - ('golden compass', 'เข็มทิศทองคำ'), - ], - 'Intermediate': [ - ('ancient scroll', 'ม้วนกระดาษโบราณ'), - ('magical key', 'กุญแจวิเศษ'), - ], - 'Advanced': [ - ('legendary artifact', 'วัตถุโบราณในตำนาน'), - ('sacred relic', 'วัตถุศักดิ์สิทธิ์'), - ] - } - }, - 'school': { - 'characters': { - 'Beginner': [ - ('new student', 'นักเรียนใหม่'), - ('kind teacher', 'คุณครูใจดี'), - ('best friend', 'เพื่อนรัก'), - ], - 'Intermediate': [ - ('class president', 'หัวหน้าห้อง'), - ('school librarian', 'บรรณารักษ์'), - ], - 'Advanced': [ - ('exchange student', 'นักเรียนแลกเปลี่ยน'), - ('science genius', 'อัจฉริยะวิทยาศาสตร์'), - ] - }, - 'locations': { - 'Beginner': [ - ('classroom', 'ห้องเรียน'), - ('school playground', 'สนามเด็กเล่น'), - ], - 'Intermediate': [ - ('science lab', 'ห้องทดลองวิทยาศาสตร์'), - ('school library', 'ห้องสมุด'), - ], - 'Advanced': [ - ('school theater', 'โรงละครโรงเรียน'), - ('computer room', 'ห้องคอมพิวเตอร์'), - ] - }, - 'objects': { - 'Beginner': [ - ('new book', 'หนังสือเล่มใหม่'), - ('special pencil', 'ดินสอวิเศษ'), - ], - 'Intermediate': [ - ('science project', 'โครงงานวิทยาศาสตร์'), - ('old diary', 'ไดอารี่เก่า'), - ], - 'Advanced': [ - ('robotics kit', 'ชุดประดิษฐ์หุ่นยนต์'), - ('ancient textbook', 'ตำราเรียนโบราณ'), - ] - } - }, - 'superhero': { - 'characters': { - 'Beginner': [ - ('young hero', 'ฮีโร่ตัวน้อย'), - ('super pet', 'สัตว์เลี้ยงพลังพิเศษ'), - ('friendly sidekick', 'ผู้ช่วยฮีโร่'), - ], - 'Intermediate': [ - ('masked vigilante', 'ฮีโร่ปริศนา'), - ('super teacher', 'คุณครูพลังพิเศษ'), - ], - 'Advanced': [ - ('legendary hero', 'ฮีโร่ในตำนาน'), - ('protector of justice', 'ผู้พิทักษ์ความยุติธรรม'), - ] - }, - 'locations': { - 'Beginner': [ - ('hero school', 'โรงเรียนฮีโร่'), - ('secret hideout', 'ที่ซ่อนลับ'), - ], - 'Intermediate': [ - ('hero headquarters', 'ศูนย์บัญชาการฮีโร่'), - ('training ground', 'สนามฝึกซ้อม'), - ], - 'Advanced': [ - ('hero academy', 'สถาบันฝึกฮีโร่'), - ('crisis center', 'ศูนย์รับมือวิกฤต'), - ] - }, - 'objects': { - 'Beginner': [ - ('hero mask', 'หน้ากากฮีโร่'), - ('power crystal', 'คริสตัลพลัง'), - ], - 'Intermediate': [ - ('super gadget', 'อุปกรณ์พิเศษ'), - ('power suit', 'ชุดพลังพิเศษ'), - ], - 'Advanced': [ - ('ultimate weapon', 'อาวุธสุดยอด'), - ('ancient power source', 'แหล่งพลังโบราณ'), - ] - } - }, - 'mystery': { - 'characters': { - 'Beginner': [ - ('young detective', 'นักสืบตัวน้อย'), - ('mysterious neighbor', 'เพื่อนบ้านปริศนา'), - ('helpful friend', 'เพื่อนผู้ช่วย'), - ], - 'Intermediate': [ - ('famous detective', 'นักสืบชื่อดัง'), - ('mystery writer', 'นักเขียนนิยายปริศนา'), - ], - 'Advanced': [ - ('master detective', 'ยอดนักสืบ'), - ('crime expert', 'ผู้เชี่ยวชาญคดี'), - ] - }, - 'locations': { - 'Beginner': [ - ('old house', 'บ้านเก่า'), - ('mystery shop', 'ร้านค้าปริศนา'), - ], - 'Intermediate': [ - ('abandoned building', 'ตึกร้าง'), - ('detective office', 'สำนักงานนักสืบ'), - ], - 'Advanced': [ - ('secret laboratory', 'ห้องทดลองลับ'), - ('mystery mansion', 'คฤหาสน์ปริศนา'), - ] - }, - 'objects': { - 'Beginner': [ - ('mysterious letter', 'จดหมายปริศนา'), - ('strange footprint', 'รอยเท้าประหลาด'), - ], - 'Intermediate': [ - ('ancient cipher', 'รหัสลับโบราณ'), - ('hidden clue', 'เบาะแสซ่อนเร้น'), - ], - 'Advanced': [ - ('secret document', 'เอกสารลับ'), - ('mysterious artifact', 'วัตถุปริศนา'), - ] - } - }, - 'science': { - 'characters': { - 'Beginner': [ - ('young scientist', 'นักวิทยาศาสตร์ตัวน้อย'), - ('robot friend', 'หุ่นยนต์เพื่อนรัก'), - ('curious student', 'นักเรียนช่างสงสัย'), - ], - 'Intermediate': [ - ('brilliant inventor', 'นักประดิษฐ์อัจฉริยะ'), - ('science teacher', 'คุณครูวิทยาศาสตร์'), - ], - 'Advanced': [ - ('famous researcher', 'นักวิจัยชื่อดัง'), - ('genius professor', 'ศาสตราจารย์อัจฉริยะ'), - ] - }, - 'locations': { - 'Beginner': [ - ('science lab', 'ห้องทดลองวิทยาศาสตร์'), - ('invention workshop', 'ห้องประดิษฐ์'), - ], - 'Intermediate': [ - ('research center', 'ศูนย์วิจัย'), - ('robotics lab', 'ห้องปฏิบัติการหุ่นยนต์'), - ], - 'Advanced': [ - ('quantum laboratory', 'ห้องปฏิบัติการควอนตัม'), - ('innovation center', 'ศูนย์นวัตกรรม'), - ] - }, - 'objects': { - 'Beginner': [ - ('colorful chemical', 'สารเคมีสีสันสดใส'), - ('strange invention', 'สิ่งประดิษฐ์แปลกใหม่'), - ], - 'Intermediate': [ - ('advanced machine', 'เครื่องจักรทันสมัย'), - ('experimental device', 'อุปกรณ์ทดลอง'), - ], - 'Advanced': [ - ('groundbreaking discovery', 'การค้นพบที่ยิ่งใ��ญ่'), - ('revolutionary invention', 'สิ่งประดิษฐ์ปฏิวัติโลก'), - ] - } - } - } - - def get_random_element(category: str) -> Tuple[str, str]: - """Get random element from the specified category for current theme and level.""" - elements = theme_elements.get(theme_id, {}).get(category, {}).get(level, []) - if not elements: - elements = theme_elements.get(theme_id, {}).get(category, {}).get('Beginner', []) - return random.choice(elements) if elements else ('', '') - - # Get random elements - character_en, character_th = get_random_element('characters') - location_en, location_th = get_random_element('locations') - object_en, object_th = get_random_element('objects') + except Exception as e: + logging.error(f"Error updating session stats: {str(e)}") + return False - # Templates for different levels - templates = { +# === 4. UTILITY FUNCTIONS === +def generate_story_continuation(user_input: str, level: str) -> str: + """Generate AI story continuation using ChatGPT""" + + level_context = { 'Beginner': { - 'en': [ - f"One day, {character_en} found {object_en} in {location_en}...", - f"In {location_en}, {character_en} saw something special...", - f"{character_en} was walking in {location_en} when suddenly...", - f"The story begins when {character_en} discovered {object_en}...", - ], - 'th': [ - f"วันหนึ่ง {character_th}เจอ{object_th}ใน{location_th}...", - f"ที่{location_th} {character_th}เห็นบางสิ่งที่พิเศษ...", - f"{character_th}กำลังเดินอยู่ใน{location_th} เมื่อจู่ๆ...", - f"เรื่องราวเริ่มต้นเมื่อ{character_th}ค้นพบ{object_th}...", - ] + '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': { - 'en': [ - f"While exploring {location_en}, {character_en} discovered {object_en} that seemed magical...", - f"The story begins when {character_en} encountered a mysterious {object_en} in {location_en}...", - f"Something strange was happening in {location_en}, and {character_en} knew it had to do with {object_en}...", - f"Nobody knew why {character_en} found {object_en} in {location_en}, but...", - ], - 'th': [ - f"ขณะที่สำรวจ{location_th} {character_th}ได้ค้นพบ{object_th}ที่ดูมีเวทมนตร์...", - f"เรื่องราวเริ่มต้นเมื่อ{character_th}พบกับ{object_th}ที่ลึกลับใน{location_th}...", - f"มีบางสิ่งประหลาดเกิดขึ้นใน{location_th} และ{character_th}รู้ว่ามันเกี่ยวข้องกับ{object_th}...", - f"ไม่มีใครรู้ว่าทำไม{character_th}ถึงพบ{object_th}ใน{location_th} แต่...", - ] + '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': { - 'en': [ - f"Legend speaks of {object_en} hidden within {location_en}, and {character_en} was destined to find it...", - f"In the depths of {location_en}, {character_en} uncovered an ancient mystery surrounding {object_en}...", - f"As {character_en} ventured through {location_en}, the secrets of {object_en} began to unfold...", - f"The discovery of {object_en} by {character_en} in {location_en} would change everything...", - ], - 'th': [ - f"ตำนานเล่าขานถึง{object_th}ที่ซ่อนอยู่ใน{location_th} และ{character_th}ถูกลิขิตให้เป็นผู้ค้นพบ...", - f"ในส่วนลึกของ{location_th} {character_th}ได้พบกับปริศนาโบราณเกี่ยวกับ{object_th}...", - f"ขณะที่{character_th}ผจญภัยใน{location_th} ความลับของ{object_th}ก็เริ่มเผยออกมา...", - f"การค้นพบ{object_th}โดย{character_th}ใน{location_th}จะเปลี่ยนแปลงทุกสิ่ง...", - ] - } - } - - # Select random template for the current level - level_templates = templates.get(level, templates['Beginner']) - starter_en = random.choice(level_templates['en']) - starter_th = random.choice(level_templates['th']) - - return { - 'en': starter_en, - 'th': starter_th - } - -def convert_sets_to_lists(obj): - """แปลง set เป็น list สำหรับการ serialize เป็น JSON""" - if isinstance(obj, set): - return list(obj) - elif isinstance(obj, dict): - return {key: convert_sets_to_lists(value) for key, value in obj.items()} - elif isinstance(obj, list): - return [convert_sets_to_lists(item) for item in obj] - return obj - -def get_theme_story_starter(theme_id: str, level: str) -> Dict[str, str]: - """Get a dynamically generated story starter for the selected theme and level.""" - return generate_dynamic_story_starter(theme_id, level) - -def get_theme_vocabulary(theme_id: str, level: str) -> List[Dict[str, str]]: - """ - Get theme-specific vocabulary with Thai translations and example sentences. - Returns list of dictionaries containing word, type, thai meaning, and example. - """ - vocab_data = { - 'fantasy': { - 'Beginner': [ - { - 'word': 'magic', - 'type': 'noun', - 'thai': 'เวทมนตร์', - 'example_en': 'The magic made flowers appear.', - 'example_th': 'เวทมนตร์ทำให้ดอกไม้ปรากฏขึ้น' - }, - { - 'word': 'wizard', - 'type': 'noun', - 'thai': 'พ่อมด', - 'example_en': 'The wizard waved his wand.', - 'example_th': 'พ่อมดโบกไม้กายสิทธิ์' - } - ], - 'Intermediate': [ - { - 'word': 'enchanted', - 'type': 'adjective', - 'thai': 'ที่ถูกสะกดด้วยเวทมนตร์', - 'example_en': 'They found an enchanted forest.', - 'example_th': 'พวกเขาพบป่าที่ถูกสะกดด้วยเวทมนตร์' - } - ] - }, - 'mystery': { - 'Beginner': [ - { - 'word': 'clue', - 'type': 'noun', - 'thai': 'เบาะแส', - 'example_en': 'The detective found an important clue.', - 'example_th': 'นักสืบพบเบาะแสสำคัญ' - } - ] + '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 } } - # แสดงคำศัพท์พร้อมตัวอย่างประโยคในหน้า UI - def show_vocabulary(word_list: List[Dict[str, str]]): - st.markdown("### 📚 คำศัพท์น่ารู้ประจำธีม") - for word_data in word_list: - st.markdown(f""" -
- {word_data['word']} ({word_data['type']}) - {word_data['thai']} -
- - 🇬🇧 {word_data['example_en']}
- 🇹🇭 {word_data['example_th']} -
-
- """, unsafe_allow_html=True) - - # ปรับปรุงฟังก์ชัน show_theme_vocabulary - def show_theme_vocabulary(): - if st.session_state.current_theme: - vocab_list = vocab_data.get(st.session_state.current_theme, {}).get(st.session_state.level, []) - if vocab_list: - show_vocabulary(vocab_list) + 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 + ) - # เพิ่มปุ่มสำหรับฝึกคำศัพท์ - if st.button("🎯 ฝึกคำศัพท์"): - practice_vocabulary(vocab_list) - - # เพิ่มฟังก์ชันฝึกคำศัพท์ - def practice_vocabulary(vocab_list: List[Dict[str, str]]): - st.markdown("### 🎮 ฝึกคำศัพท์") - - # สุ่มคำศัพท์มาทำแบบฝึกหัด - word = random.choice(vocab_list) - - # สร้างตัวเลือก - choices = [word['thai']] # คำตอบที่ถูก - other_words = [w['thai'] for w in vocab_list if w['thai'] != word['thai']] - choices.extend(random.sample(other_words, min(3, len(other_words)))) - random.shuffle(choices) - - st.write(f"คำว่า '{word['word']}' แปลว่าอะไร?") + # 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") - for choice in choices: - if st.button(choice): - if choice == word['thai']: - st.success("🎉 ถูกต้อง!") - st.write(f"ตัวอย่างประโยค: {word['example_en']}") - st.write(f"แปล: {word['example_th']}") - else: - st.error("❌ ลองใหม่อีกครั้ง") - - return vocab_data.get(theme_id, {}).get(level, []) - -# Add to session state initialization -def init_theme_state(): - 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 + except Exception as e: + logging.error(f"Error generating story continuation: {str(e)}") + return "I'm having trouble continuing the story. Please try again." -# Theme Selection UI -def show_theme_selection(): - """แสดงหน้าเลือกธีมพร้อม animations และ interactive elements""" +def provide_feedback(text: str, level: str) -> Dict[str, str]: + """Provide feedback on the user's writing with appropriate level""" - # Add custom CSS - st.markdown(""" - - """, unsafe_allow_html=True) - - # Header - st.markdown(""" -
-

🎨 เลือกธีมเรื่องราว | Choose Story Theme

-

เลือกโลกแห่งจินตนาการที่คุณต้องการผจญภัย และเริ่มต้นเขียนเรื่องราวของคุณ

-
- """, unsafe_allow_html=True) - - # Filter themes based on current level - available_themes = [ - theme for theme in story_themes.values() - if st.session_state.level in theme['level_range'] - ] - - # Create rows of 4 themes each - for i in range(0, len(available_themes), 4): - cols = st.columns(4) - row_themes = available_themes[i:i+4] - - for col, theme in zip(cols, row_themes): - with col: - # Theme Card Container - st.markdown(f""" -
-
{st.session_state.level}
-
{theme['icon']}
-
{theme['name_th']}
-
{theme['description_th']}
- -
-

- 🎯 ตัวอย่างคำศัพท์:
- {', '.join(theme['vocabulary'][st.session_state.level][:3])}... -

-
-
- """, unsafe_allow_html=True) - - # Theme Selection Button - if st.button( - f"เลือกธีม {theme['name_th']}", - key=f"theme_{theme['id']}", - use_container_width=True, - help=f"คลิกเพื่อเริ่มเขียนเรื่องราวในธีม {theme['name_th']}" - ): - try: - with st.spinner("กำลังเตรียมเรื่องราว..."): - st.session_state.current_theme = theme['id'] - starter = generate_dynamic_story_starter( - theme['id'], - st.session_state.level - ) - st.session_state.story = [{ - "role": "AI", - "content": starter['en'], - "thai_content": starter['th'], - "is_starter": True - }] - st.success(f"เลือกธีม {theme['name_th']} เรียบร้อยแล้ว!") - st.rerun() - except Exception as e: - st.error(f"เกิดข้อผิดพลาดในการเลือกธีม: {str(e)}") - logging.error(f"Theme selection error: {str(e)}") - - # Help Text - st.markdown(""" -
-

💡 เคล็ดลับ: เลือกธีมที่คุณสนใจและอยากเขียนถึงที่สุด

-

- แต่ละธีมมีคำศัพท์และแนวเรื่องที่แตกต่างกัน เหมาะสำหรับการฝึกเขียนในบริบทต่างๆ -

-
- """, unsafe_allow_html=True) - - # Error Handling - if not available_themes: - st.warning(""" - ⚠️ ไม่พบธีมที่เหมาะสมกับระดับปัจจุบัน - กรุณาเลือกระดับการเรียนรู้ใหม่หรือติดต่อผู้ดูแลระบบ - """) - -# Add theme-specific CSS -def add_theme_css(): - st.markdown(""" - - """, unsafe_allow_html=True) - -# Theme-specific vocabulary suggestions -def show_theme_vocabulary(): - if st.session_state.current_theme: - vocab_list = get_theme_vocabulary( - st.session_state.current_theme, - st.session_state.level - ) - if vocab_list: - st.markdown("### 📚 Theme Vocabulary") - for word in vocab_list: - st.markdown(f"- {word}") - -def show_welcome_section(): - st.markdown(""" -
-
- 🌟 ยินดีต้อนรับสู่ JoyStory - แอพฝึกเขียนภาษาอังกฤษแสนสนุก! -
- เรียนรู้ภาษาอังกฤษผ่านการเขียนเรื่องราวด้วยตัวเอง พร้อมผู้ช่วย AI ที่จะช่วยแนะนำและให้กำลังใจ -
-
- Welcome to JoyStory - Fun English Writing Adventure! -
-
- """, unsafe_allow_html=True) - -def show_parent_guide(): - with st.expander("📖 คำแนะนำสำหรับผู้ปกครอง | Parent's Guide"): - st.markdown(""" -
-

คำแนะนำในการใช้งาน

- -

💡 เคล็ดลับ: ให้น้องๆ พูดเรื่องราวที่อยากเขียนเป็นภาษาไทยก่อน - แล้วค่อยๆ ช่วยกันแปลงเป็นภาษาอังกฤษ

-
- """, unsafe_allow_html=True) - -def generate_story_continuation(user_input: str, level: str) -> str: - """Generate AI story continuation using ChatGPT with level-appropriate content.""" - level_context = { + level_prompts = { 'Beginner': """ - Role: You are a teaching assistant for Thai students in grades 1-3. - Rules: - - Use only 1-2 VERY simple sentences - - Use Present Simple Tense only - - Use basic vocabulary (family, school, daily activities) - - Each sentence should be 5-7 words maximum - - Focus on clear, basic responses - Example responses: - - "The cat sits under the tree." - - "The boy plays with his toy car." - - "They walk to school together." + 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': """ - Role: You are a teaching assistant for Thai students in grades 4-6. - Rules: - - Use exactly 2 sentences maximum - - Can use Present or Past Tense - - Keep each sentence under 12 words - - Use grade-appropriate vocabulary - - Add simple descriptions but stay concise - Example responses: - - "The brown cat jumped over the tall wooden fence. It landed softly in the garden." - - "Tom carefully opened his mysterious new book. The colorful pages showed amazing magical creatures." + Focus on: + - Sentence variety + - Past Tense usage + - Vocabulary appropriateness + - Basic punctuation + - Simple conjunctions + Provide moderately detailed feedback in Thai. """, 'Advanced': """ - Role: You are a teaching assistant for Thai students in grades 7-9. - Rules: - - Use 2-3 sentences maximum (no more!) - - Various tenses are allowed - - No strict word limit per sentence, but keep overall response concise - - Use more sophisticated vocabulary and sentence structures - - Create engaging responses that encourage creative continuation - - Focus on quality and natural flow rather than sentence length - Example responses: - - "Sarah discovered an ancient-looking letter hidden beneath the creaky floorboards. The yellowed paper contained a mysterious message." - - "As the storm clouds gathered overhead, James remembered the old legend about the mountain. Lightning illuminated the winding path that led to the cave entrance." + Focus on: + - Complex sentence structures + - Various tense usage + - Advanced vocabulary + - All punctuation + - Style and flow + Provide comprehensive feedback in Thai. """ } - + try: - story_context = '\n'.join([entry['content'] for entry in st.session_state.story[-3:]]) # Only use last 3 entries - - response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": f"""You are a storytelling assistant for Thai students. - {level_context[level]} - CRUCIAL GUIDELINES: - - NEVER exceed the maximum number of 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 + 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 + ) - Remember: This is interactive storytelling - let the student drive the story forward."""}, - {"role": "user", "content": f"Story context (recent):\n{story_context}\nStudent's input:\n{user_input}\nProvide a brief continuation:"} - ], - max_tokens={ - 'Beginner': 30, - 'Intermediate': 40, - 'Advanced': 50 - }[level], - temperature=0.7, - presence_penalty=0.6, # Discourage repetitive responses - frequency_penalty=0.6 # Encourage diversity in responses - ) - - # Additional length check and cleanup - 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 with proper punctuation - response_text = '. '.join(sentences) + '.' - - return response_text + # 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: - st.error(f"Error generating story continuation: {str(e)}") - return "I'm having trouble continuing the story. Please try again." - -# ฟังก์ชันสำหรับแก้ไขประโยค -def apply_correction(story_index: int, corrected_text: str): - """Apply correction to a specific story entry.""" - if 0 <= story_index < len(st.session_state.story): - 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.success("✅ แก้ไขประโยคเรียบร้อยแล้ว!") - - # อัพเดทสถิติ - if 'stats' in st.session_state: - st.session_state.stats['corrections_made'] = \ - st.session_state.stats.get('corrections_made', 0) + 1 - -# เพิ่ม CSS สำหรับการแสดงผล -st.markdown(""" - -""", unsafe_allow_html=True) + logging.error(f"Error generating feedback: {str(e)}") + return { + "feedback": "⚠️ ระบบไม่สามารถวิเคราะห์ประโยคได้ กรุณาลองใหม่อีกครั้ง", + "corrected": text, + "has_errors": False, + "error_types": [], + "difficulty_score": 5 + } -def get_vocabulary_suggestions() -> List[str]: - """Get contextual vocabulary suggestions with Thai translations.""" +def get_vocabulary_suggestions(context: str = "", level: str = DEFAULT_LEVEL) -> List[str]: + """Get contextual vocabulary suggestions with Thai translations""" try: - recent_story = '\n'.join([entry['content'] for entry in st.session_state.story[-3:]] if st.session_state.story else "Story just started") + 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 with their Thai translations and examples. - Format each suggestion as: - word (type) - คำแปล | example sentence - Make sure words match {st.session_state.level} level."""}, - {"role": "user", "content": f"Story context:\n{recent_story}\n\nSuggest 5 relevant words with Thai translations:"} + { + "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 ) - return response.choices[0].message.content.split('\n') + + suggestions = response.choices[0].message.content.split('\n') + return [s.strip() for s in suggestions if s.strip()] + except Exception as e: - st.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"] - -# In the main UI section, update how vocabulary suggestions are displayed: - if st.button("Get Vocabulary Ideas"): - vocab_suggestions = get_vocabulary_suggestions() - st.markdown("#### 📚 Suggested Words") - for word in vocab_suggestions: - st.markdown(f"• *{word}*") - -# And update how feedback is displayed to be more concise: - if st.session_state.feedback: - st.markdown(""" -
- 📝 {} -
- """.format(st.session_state.feedback), unsafe_allow_html=True) + 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" + ] -# Update the creative prompt function def get_creative_prompt() -> Dict[str, str]: - """Generate a short, simple bilingual creative prompt.""" + """Generate a bilingual creative writing prompt""" try: response = client.chat.completions.create( model="gpt-4o-mini", messages=[ - {"role": "system", "content": """Create very short story prompts in both English and Thai. - Keep it simple and under 6 words each. - Example formats: - - "What did the cat find?" - - "Where did they go next?" - - "How does the story end?" - """}, - {"role": "user", "content": "Generate a simple, short story prompt:"} + { + "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 + + 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 simple and natural:"}, - {"role": "user", "content": prompt_eng} + { + "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 - return {"eng": prompt_eng, "thai": prompt_thai} + prompt_thai = response_thai.choices[0].message.content.strip() + + return { + "eng": prompt_eng, + "thai": prompt_thai + } + except Exception as e: - st.error(f"Error generating creative prompt: {str(e)}") + logging.error(f"Error generating creative prompt: {str(e)}") return { "eng": "What happens next?", "thai": "แล้วอะไรจะเกิดขึ้นต่อ?" } -def provide_feedback(text: str, level: str) -> Dict[str, str]: - """Provide feedback and corrected sentence.""" +def calculate_text_metrics(text: str) -> Dict[str, float]: + """Calculate various metrics for the given text""" try: - response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": f"""You are a Thai English teacher helping {level} students. - Your task is to review the student's sentence and provide feedback. - - Return your response in this EXACT format only (must be valid JSON): - {{ - "feedback": "(ข้อเสนอแนะภาษาไทย)", - "corrected": "(ประโยคภาษาอังกฤษที่ถูกต้อง)", - "has_errors": true/false - }} - - Example 1 - with error: - {{ - "feedback": "คำว่า 'go' เมื่อพูดถึงเหตุการณ์ในอดีต ต้องเปลี่ยนเป็น 'went'", - "corrected": "I went to school yesterday.", - "has_errors": true - }} - - Example 2 - no error: - {{ - "feedback": "เขียนได้ถูกต้องแล้วค่ะ ประโยคสื่อความหมายได้ดี", - "corrected": "The cat is sleeping.", - "has_errors": false - }}"""}, - {"role": "user", "content": f"Review this sentence and provide feedback in the specified JSON format: {text}"} - ], - max_tokens=200, - temperature=0.3 - ) + words = text.split() + sentences = [s.strip() for s in text.split('.') if s.strip()] - # Get the response text - response_text = response.choices[0].message.content.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 - try: - # แสดง debug info เพื่อตรวจสอบ response - st.write("Debug - Response received:", response_text) - - # Parse JSON response - feedback_data = json.loads(response_text) - - # Validate required fields - if all(key in feedback_data for key in ['feedback', 'corrected', 'has_errors']): - return feedback_data - else: - raise ValueError("Missing required fields in response") - - except json.JSONDecodeError as json_err: - st.error(f"Debug - JSON Error: {str(json_err)}") - raise - except Exception as e: - st.error(f"Debug - Error: {str(e)}") + logging.error(f"Error calculating text metrics: {str(e)}") return { - "feedback": "⚠️ ระบบไม่สามารถวิเคราะห์ประโยคได้ กรุณาลองใหม่อีกครั้ง", - "corrected": text, - "has_errors": False + 'word_count': 0, + 'sentence_count': 0, + 'average_word_length': 0, + 'unique_words': 0, + 'complexity_score': 0 } -def update_points(is_correct_first_try: bool): - """อัพเดตคะแนนตามผลการเขียน""" - 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 - 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 - ) - -def show_story(): - """Display the story with proper formatting.""" - story_display = st.container() - - with story_display: - if not st.session_state.story: - st.info("เลือกธีมเรื่องราวที่ต้องการเพื่อเริ่มต้นการผจญภัย!") - else: - # แสดงข้อความทั้งหมดตามลำดับ - for entry in st.session_state.story: - if entry['role'] == 'AI': - if entry.get('is_starter'): - # แสดงประโยคเริ่มต้นพิเศษ - st.markdown(f""" -
-

🎬 เริ่มเรื่อง:

-

{entry.get('thai_content', '')}

-

{entry['content']}

-
- """, unsafe_allow_html=True) - else: - # แสดงข้อความ AI ปกติ - st.markdown(f""" -
-

🤖 AI: {entry['content']}

-
- """, unsafe_allow_html=True) - elif entry['role'] == 'You': - # กำหนดสถานะและสไตล์การแสดงผล - status_icon = "✅ " if entry.get('is_correct') else "✍️ " - bg_color = "#f0f9ff" if entry.get('is_corrected') else "white" - - st.markdown(f""" -
-

👤 You: {status_icon}{entry['content']}

-
- """, unsafe_allow_html=True) - -# Add CSS for consistent styling -st.markdown(""" - -""", unsafe_allow_html=True) +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 -# แสดงผลคะแนนและความสำเร็จ -def show_achievements(): - """แสดงความสำเร็จและสถิติ""" - with st.container(): - # 1. แสดงคะแนนรวมและ Streak - st.markdown(f""" -
-

🌟 คะแนนรวม: {st.session_state.points['total']}

-

Streak ปัจจุบัน: {st.session_state.points['streak']} ประโยค

-

Streak สูงสุด: {st.session_state.points['max_streak']} ประโยค

+# === 5. UI COMPONENTS === +def show_welcome_section(): + """Display welcome message and introduction""" + st.markdown(""" +
+
+ 🌟 ยินดีต้อนรับสู่ JoyStory - แอพฝึกเขียนภาษาอังกฤษแสนสนุก!
- """, unsafe_allow_html=True) - - # 2. แสดงสถิติการเขียน - st.markdown(""" -
-

📊 สถิติการเขียน

+
+ เรียนรู้ภาษาอังกฤษผ่านการเขียนเรื่องราวด้วยตัวเอง + พร้อมผู้ช่วย AI ที่จะช่วยแนะนำและให้กำลังใจ
- """, unsafe_allow_html=True) - - col1, col2 = st.columns(2) - with col1: - st.metric( - "ประโยคที่เขียนทั้งหมด", - st.session_state.stats['total_sentences'], - help="จำนวนประโยคทั้งหมดที่คุณได้เขียน" - ) - st.metric( - "ถูกต้องตั้งแต่แรก", - st.session_state.stats['correct_first_try'], - help="จำนวนประโยคที่ถูกต้องโดยไม่ต้องแก้ไข" - ) - with col2: - st.metric( - "ความแม่นยำ", - f"{st.session_state.stats['accuracy_rate']:.1f}%", - help="เปอร์เซ็นต์ของประโยคที่ถูกต้องตั้งแต่แรก" - ) - st.metric( - "คำศัพท์ที่ใช้", - len(st.session_state.stats['vocabulary_used']), - help="จำนวนคำศัพท์ที่ไม่ซ้ำกันที่คุณได้ใช้" - ) - - # 3. แสดงความสำเร็จ - st.markdown(""" -
-

🏆 ความสำเร็จ

+
+ Welcome to JoyStory - Fun English Writing Adventure!
- """, unsafe_allow_html=True) - - if st.session_state.achievements: - for achievement in st.session_state.achievements: - st.success(achievement) - else: - st.info("ยังไม่มีความสำเร็จ - เขียนต่อไปเพื่อปลดล็อกรางวัล!") +
+ """, unsafe_allow_html=True) - # 4. แสดงความสำเร็จที่ยังไม่ได้รับ (ตัวเลือก) - if not st.session_state.achievements: # ถ้ายังไม่มีความสำเร็จ - st.markdown(""" -
- เป้าหมายที่จะได้รับความสำเร็จ: +def show_parent_guide(): + """Display guide for parents""" + with st.expander("📖 คำแนะนำสำหรับผู้ปกครอง | Parent's Guide"): + st.markdown(""" +
+

คำแนะนำในการใช้งาน

+
    +
  • + 👥 การมีส่วนร่วม: แนะนำให้นั่งเขียนเรื่องราวร่วมกับน้องๆ +
  • +
  • + 💡 การช่วยเหลือ: ช่วยอธิบายคำแนะนำและคำศัพท์ที่น้องๆ ไม่เข้าใจ +
  • +
  • + 🌟 การให้กำลังใจ: ให้กำลังใจและชื่นชมเมื่อน้องๆ เขียนได้ดี +
  • +
  • + ⏱️ เวลาที่เหมาะสม: ใช้เวลาในการเขียนแต่ละครั้งไม่เกิน 20-30 นาที +
  • +
+
+

+ 💡 เคล็ดลับ: ให้น้องๆ พูดเรื่องราวที่อยากเขียนเป็นภาษาไทยก่อน + แล้วค่อยๆ ช่วยกันแปลงเป็นภาษาอังกฤษ +

- """, unsafe_allow_html=True) - st.markdown(""" - - 🌟 นักเขียนไร้ที่ติ: เขียนถูกต้อง 5 ประโยคติดต่อกัน - - 📚 ราชาคำศัพท์: ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ - - 📖 นักแต่งนิทาน: เขียนเรื่องยาว 10 ประโยค - - 👑 ราชาความแม่นยำ: มีอัตราความถูกต้อง 80% ขึ้นไป - """) - -def update_achievements(): - """ตรวจสอบและอัพเดตความสำเร็จ""" - 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("🎉 ได้รับความสำเร็จใหม่: ราชาความแม่นยำ!") - -def create_story_pdf(): - """สร้าง PDF จากเรื่องราวที่เขียน""" - buffer = io.BytesIO() - doc = SimpleDocTemplate( - buffer, - pagesize=A4, - rightMargin=72, - leftMargin=72, - topMargin=72, - bottomMargin=72 - ) - - # สร้าง styles สำหรับ PDF - styles = getSampleStyleSheet() - title_style = ParagraphStyle( - 'CustomTitle', - parent=styles['Heading1'], - fontSize=24, - spaceAfter=30, - alignment=1 # center - ) - - story_style = ParagraphStyle( - 'StoryText', - parent=styles['Normal'], - fontSize=12, - spaceBefore=12, - spaceAfter=12, - leading=16 - ) - - stats_style = ParagraphStyle( - 'Stats', - parent=styles['Normal'], - fontSize=10, - textColor=colors.gray, - alignment=1 - ) - - # สร้างเนื้อหา PDF - elements = [] - - # หน้าปก - elements.append(Paragraph("My English Story Adventure", title_style)) - elements.append(Paragraph( - f"Created by: {st.session_state.get('player_name', 'Young Writer')}
" - f"Date: {datetime.now().strftime('%B %d, %Y')}
" - f"Level: {st.session_state.level}", - stats_style - )) - elements.append(Spacer(1, 30)) - - # เพิ่มสถิติการเขียน - stats_text = ( - f"Total Points: {st.session_state.points['total']}
" - f"Perfect Sentences: {st.session_state.points['perfect_sentences']}
" - f"Accuracy Rate: {st.session_state.stats['accuracy_rate']:.1f}%
" - f"Unique Words Used: {len(st.session_state.stats['vocabulary_used'])}" - ) - elements.append(Paragraph(stats_text, stats_style)) - elements.append(Spacer(1, 30)) - - # เพิ่มเนื้อเรื่อง - elements.append(Paragraph("The Story", styles['Heading2'])) - for entry in st.session_state.story: - if entry['role'] == 'You': - text = f"👤 {entry['content']}" - if entry.get('is_correct'): - style = ParagraphStyle( - 'Correct', - parent=story_style, - textColor=colors.darkgreen - ) - else: - style = story_style - else: # AI response - text = f"🤖 {entry['content']}" - style = ParagraphStyle( - 'AI', - parent=story_style, - textColor=colors.navy - ) - elements.append(Paragraph(text, style)) - - # เพิ่ม achievements ที่ได้รับ - if hasattr(st.session_state, 'achievements') and st.session_state.achievements: - elements.append(Spacer(1, 20)) - elements.append(Paragraph("Achievements Earned", styles['Heading2'])) - for achievement in st.session_state.achievements: - elements.append(Paragraph(f"🏆 {achievement}", stats_style)) - - # สร้าง PDF - doc.build(elements) - pdf = buffer.getvalue() - buffer.close() - return pdf - -# เพิ่มฟังก์ชันโหลดความก้าวหน้า -def load_progress(uploaded_file): - """โหลดความก้าวหน้าจากไฟล์ JSON""" - try: - data = json.loads(uploaded_file.getvalue()) - st.session_state.level = data['level'] - st.session_state.story = data['story'] - st.session_state.achievements = data['achievements'] - st.session_state.points = data['points'] - 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']) # แปลง list กลับเป็น set - } - st.success("โหลดความก้าวหน้าเรียบร้อย!") - st.rerun() - except Exception as e: - st.error(f"เกิดข้อผิดพลาดในการโหลดไฟล์: {str(e)}") - -def reset_story(): - """Reset the story and related state variables.""" - st.session_state.story = [] - st.session_state.feedback = None - st.session_state.unique_words = set() - st.session_state.total_words = 0 - st.session_state.achievements = [] # เปลี่ยนจาก badges เป็น achievements - st.session_state.should_reset = False - - # เพิ่มการ 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() - } - -# Handle story reset if needed -if st.session_state.should_reset: - reset_story() - -# Main UI Layout - -# Initialize session state -init_session_state() -init_theme_state() -add_theme_css() - -# Main UI Layout -st.markdown("# 📖 JoyStory") -show_welcome_section() -show_parent_guide() - -# After showing the welcome section and before the main story area -if not st.session_state.current_theme: - show_theme_selection() -else: - # Show current theme and option to change - if st.button("🔄 Change Theme"): - st.session_state.current_theme = None - st.rerun() - - # Show theme-specific story starter if no story has been started - if not st.session_state.story and st.session_state.theme_story_starter: - st.markdown(f""" -
-

{st.session_state.theme_story_starter['th']}

-

{st.session_state.theme_story_starter['en']}

""", unsafe_allow_html=True) -# Sidebar for settings -with st.sidebar: - st.markdown("### 📂 โหลดความก้าวหน้า") - uploaded_file = st.file_uploader( - "เลือกไฟล์ .json", - type=['json'], - help="เลือกไฟล์ความก้าวหน้าที่บันทึกไว้" - ) - if uploaded_file: - if st.button("โหลดความก้าวหน้า"): - load_progress(uploaded_file) - +def show_level_selection(): + """Display level selection interface""" st.markdown(""" -
+
🎯 เลือกระดับการเรียนรู้
""", unsafe_allow_html=True) level = st.radio( - "ระดับการเรียน", # เพิ่ม label + "ระดับการเรียน", options=list(level_options.keys()), format_func=lambda x: level_options[x]['thai_name'], - label_visibility="collapsed" # ซ่อน label แต่ยังคงมีสำหรับ accessibility + key="level_selector", + label_visibility="collapsed" ) - # แสดงคำอธิบายระดับ + # Show level details st.markdown(f""" -
- 🎓 {level_options[level]['description']} -
📚 เหมาะสำหรับอายุ: {level_options[level]['age_range']} +
+

+ {level_options[level]['thai_name']} +

+

+ 🎯 {level_options[level]['description']} +

+

+ 👥 เหมาะสำหรับอายุ: {level_options[level]['age_range']} +

+
+
✨ คุณลักษณะ:
+
    + {' '.join([f'
  • • {feature}
  • ' for feature in level_options[level]['features']])} +
+
""", unsafe_allow_html=True) - st.session_state.level = level - - if st.button("เริ่มเรื่องใหม่ | Start New Story"): - st.session_state.should_reset = True - st.rerun() + return level + +def show_theme_selection(): + """Display theme selection interface""" + st.markdown(""" +
+

+ 🎨 เลือกธีมเรื่องราว | Choose Story Theme +

+

+ เลือกโลกแห่งจินตนาการที่คุณต้องการผจญภัย และเริ่มต้นเขียนเรื่องราวของคุณ +

+
+ """, unsafe_allow_html=True) + + # Filter themes for current level + available_themes = [ + theme for theme in story_themes.values() + if st.session_state.level in theme['level_range'] + ] + + # Create rows of 4 themes each + for i in range(0, len(available_themes), 4): + cols = st.columns(4) + row_themes = available_themes[i:i+4] + for col, theme in zip(cols, row_themes): + with col: + show_theme_card(theme) -# Main content area -if not st.session_state.current_theme: - # Show theme selection if no theme is selected - show_theme_selection() -else: - # Show main story interface - col1, col2 = st.columns([3, 1]) + # Help text + st.markdown(""" +
+

+ 💡 เคล็ดลับ: เลือกธีมที่คุณสนใจและอยากเขียนถึงที่สุด +

+

+ แต่ละธีมมีคำศัพท์และแนวเรื่องที่แตกต่างกัน เหมาะสำหรับการฝึกเขียนในบริบทต่างๆ +

+
+ """, unsafe_allow_html=True) - with col1: - # Story Display Box - st.markdown(""" -
-
📖 เรื่องราวของคุณ
-
Your Story
+def show_theme_card(theme: Dict): + """Display individual theme card""" + # Theme Card Container + st.markdown(f""" +
+
+ {st.session_state.level}
- """, unsafe_allow_html=True) - - # Theme Selection หรือ Change Theme - if not st.session_state.current_theme: - show_theme_selection() - else: - # Show current theme and option to change - เพิ่ม key ให้ปุ่ม - if st.button("🔄 Change Theme", key="change_theme_button"): - st.session_state.current_theme = None - st.session_state.theme_story_starter = None - st.session_state.story = [] - st.rerun() - - # แสดงเนื้อเรื่อง - show_story() - - # Text input area - st.markdown(""" -
-
✏️ ถึงตาคุณแล้ว
-
Your Turn
-
- """, unsafe_allow_html=True) - - # เพิ่ม key ให้ input area - st.text_area( - "เขียนต่อจากเรื่องราว | Continue the story:", - height=100, - key="story_input_area", - label_visibility="collapsed" - ) - - # เพิ่ม key ให้ปุ่ม Submit - st.button("ส่งคำตอบ | Submit", key="submit_story_button", on_click=submit_story) - with col2: - # 1. Feedback Section (Most Important) - if st.session_state.feedback: - st.markdown(""" - - """, unsafe_allow_html=True) +
+ {theme['icon']} +
- feedback_data = st.session_state.feedback - if isinstance(feedback_data, dict) and feedback_data.get('has_errors'): - st.markdown(f""" -
-

- {feedback_data['feedback']} -

-

- ประโยคที่ถูกต้อง:
- - {feedback_data['corrected']} - -

-
- """, unsafe_allow_html=True) - - # แสดงปุ่มแก้ไข - if st.button("✍️ แก้ไขประโยคให้ถูกต้อง", key="correct_button"): - last_user_entry_idx = next( - (i for i, entry in reversed(list(enumerate(st.session_state.story))) - if entry['role'] == 'You'), - None - ) - if last_user_entry_idx is not None: - apply_correction( - last_user_entry_idx, - feedback_data['corrected'] - ) - st.rerun() - else: - st.markdown(f""" -
-

- {feedback_data.get('feedback', '✨ เขียนได้ถูกต้องแล้วค่ะ!')} -

-
- """, unsafe_allow_html=True) - - # 2. Writing Tools (Expandable) - with st.expander("✨ เครื่องมือช่วยเขียน | Writing Tools"): - if st.button("🎯 ขอคำใบ้"): - prompt = get_creative_prompt() - st.markdown(f""" -
-
💭 {prompt['thai']}
-
💭 {prompt['eng']}
-
- """, unsafe_allow_html=True) +
+ {theme['name_th']} +
- if st.button("📚 คำศัพท์แนะนำ"): - vocab_suggestions = get_vocabulary_suggestions() - st.markdown("#### 📚 คำศัพท์น่ารู้") - for word in vocab_suggestions: - st.markdown(f"• {word}") - - # 3. Achievements (Expandable) - with st.expander("🏆 ความสำเร็จ | Achievements"): - show_achievements() - - # 4. Save Options (At the bottom) - st.markdown("### 💾 บันทึกเรื่องราว") - if st.session_state.story: - st.markdown("### 💾 บันทึกเรื่องราว") - col1, col2 = st.columns(2) - with col1: - pdf = create_story_pdf() - st.download_button("📑 PDF", data=pdf, file_name="story.pdf", mime="application/pdf") - with col2: - story_data = { - 'level': st.session_state.level, - 'story': st.session_state.story, - 'achievements': st.session_state.achievements - } - st.download_button("💾 Save", data=json.dumps(story_data), file_name="story.json", mime="application/json") - -# Handle story reset if needed -if st.session_state.should_reset: - reset_story() +
+ {theme['description_th']} +
+
+ """, unsafe_allow_html=True) -# Add new CSS for improved layout -st.markdown(""" - -""", unsafe_allow_html=True) \ No newline at end of file + # Theme Selection Button + if st.button( + f"เลือกธีม {theme['name_th']}", + key=f"theme_{theme['id']}", + help=f"คลิกเพื่อเริ่มเขียนเรื่องราวในธีม {theme['name_th']}", + use_container_width=True + ): + try: + with st.spinner("กำลังเตรียมเรื่องราว..."): + st.session_state.current_theme = theme['id'] + starter = generate_dynamic_story_starter( + theme['id'], + st.session_state.level + ) + st.session_state.story = [{ + "role": "AI", + "content": starter['en'], + "thai_content": starter['th'], + "is_starter": True + }] + st.success(f"เลือกธีม {theme['name_th']} เรียบร้อยแล้ว!") + st.rerun() + except Exception as e: + logging.error(f"Error selecting theme: {str(e)}") + st.error("เกิดข้อผิดพลาดในการเลือกธีม กรุณาลองใหม่อีกครั้ง") \ No newline at end of file