diff --git "a/appbackup.app" "b/appbackup.app" --- "a/appbackup.app" +++ "b/appbackup.app" @@ -1,63 +1,323 @@ +# === 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 datetime import datetime - -# ระบบ Achievements -achievements_list = { - 'perfect_writer': { - 'name': '🌟 นักเขียนไร้ที่ติ', - 'description': 'เขียนถูกต้อง 5 ประโยคติดต่อกัน', - 'condition': lambda: st.session_state.points['streak'] >= 5 +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' }, - 'vocabulary_master': { - 'name': '📚 ราชาคำศัพท์', - 'description': 'ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ', - 'condition': lambda: len(st.session_state.stats['vocabulary_used']) >= 50 + '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' }, - 'quick_learner': { - 'name': '🚀 นักเรียนจอมขยัน', - 'description': 'แก้ไขประโยคให้ถูกต้องภายใน 3 วินาที', - 'condition': lambda: True # ต้องเพิ่มการจับเวลา + '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' }, - 'story_master': { - 'name': '📖 นักแต่งนิทาน', - 'description': 'เขียนเรื่องยาว 10 ประโยค', - 'condition': lambda: len(st.session_state.story) >= 10 + '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' }, - 'accuracy_king': { - 'name': '👑 ราชาความแม่นยำ', - 'description': 'มีอัตราความถูกต้อง 80% ขึ้นไป (อย่างน้อย 10 ประโยค)', - 'condition': lambda: ( - st.session_state.stats['total_sentences'] >= 10 and - st.session_state.stats['accuracy_rate'] >= 80 - ) + '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 Streamlit page configuration +# 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", + 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)', @@ -68,7 +328,10 @@ level_options = { 'คำศัพท์พื้นฐานที่ใช้ในชีวิตประจำวัน', 'มีคำแนะนำภาษาไทยละเอียด', 'เน้นการใช้ Present Simple Tense' - ] + ], + 'max_sentence_length': 10, + 'allowed_tenses': ['present_simple'], + 'feedback_level': 'detailed' }, 'Intermediate': { 'thai_name': 'ระดับกลาง (ป.4-6)', @@ -79,7 +342,10 @@ level_options = { 'เริ่มใช้ Past Tense ได้', 'คำศัพท์หลากหลายขึ้น', 'สามารถเขียนเรื่องราวต่อเนื่องได้' - ] + ], + 'max_sentence_length': 15, + 'allowed_tenses': ['present_simple', 'past_simple'], + 'feedback_level': 'moderate' }, 'Advanced': { 'thai_name': 'ระดับก้าวหน้า (ม.1-3)', @@ -90,15 +356,51 @@ level_options = { 'ใช้ 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'] + ) } } -# Add custom CSS including new styles for level selection +# Initial CSS Setup st.markdown(""" """, unsafe_allow_html=True) -# Initialize session state variables +# 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(): - if 'story' not in st.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 = [] - 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, @@ -154,403 +573,1082 @@ def init_session_state(): 'streak': 0, 'max_streak': 0 } - 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() + 'vocabulary_used': set(), + '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() - -# เพิ่มฟังก์ชันสำหรับจัดการ input -def clear_input(): - st.session_state.user_input = "" + + # 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 -# Callback function for submit button -def submit_story(): - if st.session_state.text_input.strip(): - user_text = st.session_state.text_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 + } - # เพิ่มคำศัพท์ที่ใช้ - words = set(user_text.lower().split()) - st.session_state.stats['vocabulary_used'].update(words) + 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") - # ตรวจสอบความถูกต้อง - feedback_data = provide_feedback(user_text, st.session_state.level) - is_correct = not feedback_data.get('has_errors', False) + # 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'] - # อัพเดตคะแนน - update_points(is_correct) + # 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) + } - # อัพเดต achievements - update_achievements() + # Load theme if present + if 'current_theme' in data: + st.session_state.current_theme = data['current_theme'] - # เพิ่มประโยคของผู้ใช้ - story_index = len(st.session_state.story) - st.session_state.story.append({ - "role": "You", - "content": user_text, - "is_corrected": False - }) + logging.info("Progress loaded successfully") + st.success("โหลดความก้าวหน้าเรียบร้อย!") - try: - # รับ feedback และประโยคที่ถูกต้อง - feedback_data = provide_feedback(user_text, st.session_state.level) - st.session_state.feedback = feedback_data + 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) - # ถ้ามีข้อผิดพลาด แสดงคำแนะนำ - if feedback_data['has_errors']: - st.markdown(f""" -
-

🎯 คำแนะนำ:

-

{feedback_data['feedback']}

-
- """, unsafe_allow_html=True) + # 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'] + ) - # Generate AI continuation using corrected text if there were errors - ai_response = generate_story_continuation( - feedback_data['corrected'] if feedback_data['has_errors'] else user_text, - st.session_state.level - ) - 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 - except Exception as e: - st.error("เกิดข้อผิดพลาดในการวิเคราะห์ประโยค กรุณาลองใหม่อีกครั้ง") - # Log error for debugging - st.error(f"Debug - Error in submit_story: {str(e)}") - - # Clear input - st.session_state.text_input = "" - -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) + # 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 with level-appropriate content.""" + """Generate AI story continuation using ChatGPT""" + level_context = { - '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." - """, - '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." - """, - '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." - """ + '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: - 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 - - 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 - ) + # 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" - # 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()] + # Create prompt + level_settings = level_context[level] - # 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 + 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: - st.error(f"Error generating story continuation: {str(e)}") + logging.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'] - # เก็บประวัติการแก้ไข - if 'corrections' not in st.session_state: - st.session_state.corrections = {} - - st.session_state.corrections[story_index] = { - 'original': original_text, - 'corrected': corrected_text, - 'timestamp': datetime.datetime.now().isoformat() +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'] } - # แก้ไขประโยคในเรื่อง - st.session_state.story[story_index]['content'] = corrected_text - st.session_state.story[story_index]['is_corrected'] = True - # แสดงข้อความยืนยันการแก้ไข - st.success("✅ แก้ไขประโยคเรียบร้อยแล้ว!") + except Exception as e: + logging.error(f"Error generating story starter: {str(e)}") + # Provide fallback starter + return { + 'en': 'Once upon a time...', + 'th': 'กาลครั้งหนึ่ง...' + } -def get_vocabulary_suggestions() -> List[str]: - """Get contextual vocabulary suggestions with Thai translations.""" +def update_points(is_correct_first_try: bool): + """อัพเดตคะแนนตามผลการเขียน""" try: - recent_story = '\n'.join([entry['content'] for entry in st.session_state.story[-3:]] if st.session_state.story else "Story just started") + # คำนวณคะแนนพื้นฐาน + base_points = 10 - 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:"} - ], - max_tokens=200, - temperature=0.8 + 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 ) - return response.choices[0].message.content.split('\n') + + logging.info(f"Points updated: +{points} points") + return True + 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 updating points: {str(e)}") + return False -# Update the creative prompt function -def get_creative_prompt() -> Dict[str, str]: - """Generate a short, simple bilingual creative prompt.""" +def apply_correction(story_index: int, corrected_text: str): + """แก้ไขประโยคในเรื่อง""" 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:"} - ], - max_tokens=50, - temperature=0.7 - ) - prompt_eng = response.choices[0].message.content + 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 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()] + + 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) + } - # Get the response text - response_text = response.choices[0].message.content.strip() + 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 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(""" +
+
+ 🌟 ยินดีต้อนรับสู่ JoyStory - แอพฝึกเขียนภาษาอังกฤษแสนสนุก! +
+
+ เรียนรู้ภาษาอังกฤษผ่านการเขียนเรื่องราวด้วยตัวเอง + พร้อมผู้ช่วย AI ที่จะช่วยแนะนำและให้กำลังใจ +
+
+ Welcome to JoyStory - Fun English Writing Adventure! +
+
+ """, unsafe_allow_html=True) + +def show_parent_guide(): + """Display guide for parents""" + with st.expander("📖 คำแนะนำสำหรับผู้ปกครอง | Parent's Guide"): + st.markdown(""" +
+

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

+ +
+

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

+
+
+ """, unsafe_allow_html=True) + +def show_level_selection(): + """Display level selection interface""" + st.markdown(""" +
+ 🎯 เลือกระดับการเรียนรู้ +
+ """, unsafe_allow_html=True) + + level = st.radio( + "ระดับการเรียน", + options=list(level_options.keys()), + format_func=lambda x: level_options[x]['thai_name'], + key="level_selector", + label_visibility="collapsed" ) + + # Show level details + st.markdown(f""" +
+

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

+

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

+

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

+
+
✨ คุณลักษณะ:
+ +
+
+ """, unsafe_allow_html=True) + + return level + +def show_theme_selection(): + """Display theme selection interface""" + # Header section + header_html = ''' +
+

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

+

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

+
+ ''' + st.markdown(header_html, unsafe_allow_html=True) + + # Filter available themes + available_themes = [ + theme for theme in story_themes.values() + if st.session_state.level in theme['level_range'] + ] + + # Create grid layout + num_themes = len(available_themes) + rows = (num_themes + 3) // 4 # Ceiling division to get number of rows needed + + # Create rows with 4 themes each + for row in range(rows): + cols = st.columns(4) + for col_idx, col in enumerate(cols): + theme_idx = row * 4 + col_idx + if theme_idx < num_themes: + theme = available_themes[theme_idx] + with col: + # Theme card container + theme_card = f''' +
+
+ {st.session_state.level} +
+
+ {theme['icon']} +
+
+ {theme['name_th']} +
+
+ {theme['description_th']} +
+
+ ''' + st.markdown(theme_card, unsafe_allow_html=True) + + # Theme selection button + if st.button( + f"เลือกธีม {theme['name_th']}", + key=f"theme_{theme['id']}_{row}_{col_idx}", + use_container_width=True + ): + handle_theme_selection(theme) + +def handle_theme_selection(theme: dict): + """Handle theme selection and initialization""" + 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("เกิดข้อผิดพลาดในการเลือกธีม กรุณาลองใหม่อีกครั้ง") + +def show_theme_card(theme: Dict): # เปลี่ยนจาก display_theme_card เป็น show_theme_card + """Display a single theme card with proper styling""" + card_html = f""" +
+
+ {st.session_state.level} +
+ +
+ {theme['icon']} +
+ +
+ {theme['name_th']} +
+ +
+ {theme['description_th']} +
+
+ """ + + # แสดงการ์ดธีม + st.markdown(card_html, unsafe_allow_html=True) + + # แสดงปุ่มเลือกธีม + 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("เกิดข้อผิดพลาดในการเลือกธีม กรุณาลองใหม่อีกครั้ง") + +def show_story(): + """Display the story with proper formatting""" + story_display = st.container() + + with story_display: + if not st.session_state.story: + st.info("เลือกธีมเรื่องราวที่ต้องการเพื่อเริ่มต้นการผจญภัย!") + return + + # Story Display Box + for idx, entry in enumerate(st.session_state.story): + if entry['role'] == 'AI': + if entry.get('is_starter'): + # Story Starter + st.markdown(f""" +
+
+ 🎬 เริ่มเรื่อง: +
+
+ {entry.get('thai_content', '')} +
+
+ {entry['content']} +
+
+ """, unsafe_allow_html=True) + else: + # AI Response + st.markdown(f""" +
+
+ 🤖 AI: {entry['content']} +
+
+ """, unsafe_allow_html=True) + + elif entry['role'] == 'You': + # User Entry + status_icon = "✅" if entry.get('is_correct') else "✍️" + bg_color = "#e8f5e9" if entry.get('is_correct') else "#fff" + border_color = "#4caf50" if entry.get('is_correct') else "#1e88e5" + + st.markdown(f""" +
+
+ 👤 You: {status_icon} {entry['content']} +
+ {f'
{entry.get("feedback", "")}
' if entry.get('feedback') else ''} +
+ """, unsafe_allow_html=True) + +def show_feedback_section(): + """Display writing feedback section""" + if not st.session_state.feedback: + return + + st.markdown(""" +
+
+
📝 คำแนะนำจากครู
+
Writing Feedback
+
+
+ """, unsafe_allow_html=True) + + feedback_data = st.session_state.feedback + if isinstance(feedback_data, dict) and feedback_data.get('has_errors'): + # Show error feedback + st.markdown(f""" +
+

+ {feedback_data['feedback']} +

+
+

+ ประโยคที่ถูกต้อง: +

+

+ {feedback_data['corrected']} +

+
+
+ """, unsafe_allow_html=True) + + # Correction button + if st.button( + "✍️ แก้ไขประโยคให้ถูกต้อง", + key="correct_button", + help="คลิกเพื่อแก้ไขประโยคให้ถูกต้อ���ตามคำแนะนำ" + ): + 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: + # Show success feedback + st.markdown(f""" +
+

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

+
+ """, unsafe_allow_html=True) + +def show_writing_tools(): + """Display writing tools section""" + with st.expander("✨ เครื่องมือช่วยเขียน | Writing Tools"): + col1, col2 = st.columns(2) + + with col1: + if st.button("🎯 ขอคำใบ้", use_container_width=True): + with st.spinner("กำลังสร้างคำใบ้..."): + prompt = get_creative_prompt() + st.markdown(f""" +
+
💭 {prompt['thai']}
+
+ 💭 {prompt['eng']} +
+
+ """, unsafe_allow_html=True) + + with col2: + if st.button("📚 คำศัพท์แนะนำ", use_container_width=True): + with st.spinner("กำลังค้นหาคำศัพท์..."): + vocab_suggestions = get_vocabulary_suggestions() + st.markdown("#### 📚 คำศัพท์น่ารู้") + for word in vocab_suggestions: + st.markdown(f""" +
+ • {word} +
+ """, unsafe_allow_html=True) -# แสดงผลคะแนนและความสำเร็จ def show_achievements(): - """แสดงความสำเร็จและสถิติ""" + """Display achievements and stats""" with st.container(): - # 1. แสดงคะแนนรวมและ Streak + # Points and Streak st.markdown(f""" -
-

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

-

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

-

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

+
+

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

+

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

+

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

""", unsafe_allow_html=True) - # 2. แสดงสถิติการเขียน + # Writing Stats st.markdown(""" -
+

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

""", unsafe_allow_html=True) @@ -579,9 +1677,9 @@ def show_achievements(): help="จำนวนคำศัพท์ที่���ม่ซ้ำกันที่คุณได้ใช้" ) - # 3. แสดงความสำเร็จ + # Show Achievements st.markdown(""" -
+

🏆 ความสำเร็จ

""", unsafe_allow_html=True) @@ -591,259 +1689,121 @@ def show_achievements(): st.success(achievement) else: st.info("ยังไม่มีความสำเร็จ - เขียนต่อไปเพื่อปลดล็อกรางวัล!") - - # 4. แสดงความสำเร็จที่ยังไม่ได้รับ (ตัวเลือก) - if not st.session_state.achievements: # ถ้ายังไม่มีความสำเร็จ + + # Show available achievements st.markdown(""" -
- เป้าหมายที่จะได้รับความสำเร็จ: +
+

+ รางวัลที่รอคุณอยู่: +

+
    +
  • 🌟 นักเขียนไร้ที่ติ: เขียนถูกต้อง 5 ประโยคติดต่อกัน
  • +
  • 📚 ราชาคำศัพท์: ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ
  • +
  • 📖 นักแต่งนิทาน: เขียนเรื่องยาว 10 ประโยค
  • +
  • 👑 ราชาความแม่นยำ: มีอัตราความถูกต้อง 80% ขึ้นไป
  • +
""", 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 - ) +def show_save_options(): + """Display save and export options""" + st.markdown("### 💾 บันทึกเรื่องราว") - story_style = ParagraphStyle( - 'StoryText', - parent=styles['Normal'], - fontSize=12, - spaceBefore=12, - spaceAfter=12, - leading=16 - ) + if not st.session_state.story: + st.info("เริ่มเขียนเรื่องราวก่อนเพื่อบันทึกผลงานของคุณ") + return + + col1, col2 = st.columns(2) + with col1: + # PDF Export + if st.button("📑 บันทึกเป็น PDF", use_container_width=True): + try: + pdf = create_story_pdf() + st.download_button( + "📥 ดาวน์โหลด PDF", + data=pdf, + file_name=f"story_{datetime.now().strftime('%Y%m%d')}.pdf", + mime="application/pdf" + ) + except Exception as e: + logging.error(f"Error creating PDF: {str(e)}") + st.error("เกิดข้อผิดพลาดในการสร้างไฟล์ PDF กรุณาลองใหม่อีกครั้ง") - 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 + with col2: + # JSON Save + if st.button("💾 บันทึกความก้าวหน้า", use_container_width=True): + try: + story_data = { + 'timestamp': datetime.now().isoformat(), + 'level': st.session_state.level, + 'story': st.session_state.story, + 'achievements': st.session_state.achievements, + 'stats': convert_sets_to_lists(st.session_state.stats), + 'points': st.session_state.points + } + + st.download_button( + "📥 ดาวน์โหลดไฟล์บันทึก", + data=json.dumps(story_data, ensure_ascii=False, indent=2), + file_name=f"story_progress_{datetime.now().strftime('%Y%m%d')}.json", + mime="application/json" ) - 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 = data['stats'] - st.session_state.stats['vocabulary_used'] = set(data['stats']['vocabulary_used']) - st.success("โหลดความก้าวหน้าเรียบร้อย!") - st.rerun() - except Exception as e: - st.error(f"เกิดข้อผิดพลาดในการโหลดไฟล์: {str(e)}") + except Exception as e: + logging.error(f"Error saving progress: {str(e)}") + st.error("เกิดข้อผิดพลาดในการบันทึกความก้าวหน้า กรุณาลองใหม่อีกครั้ง") -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 -st.markdown("# 📖 JoyStory") -show_welcome_section() -show_parent_guide() # Add parent guide right after welcome section - -# Sidebar for settings -with st.sidebar: - st.markdown("### 📂 โหลดความก้าวหน้า") - uploaded_file = st.file_uploader( +def show_sidebar(): + """Display sidebar content""" + st.sidebar.markdown("### ⚙️ การตั้งค่า") + + # Progress Upload + st.sidebar.markdown("#### 📂 โหลดความก้าวหน้า") + uploaded_file = st.sidebar.file_uploader( "เลือกไฟล์ .json", type=['json'], help="เลือกไฟล์ความก้าวหน้าที่บันทึกไว้" ) if uploaded_file: - if st.button("โหลดความก้าวหน้า"): - load_progress(uploaded_file) - - 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 - ) + if st.sidebar.button("โหลดความก้าวหน้า", use_container_width=True): + try: + data = json.loads(uploaded_file.getvalue()) + load_progress(data) + st.sidebar.success("โหลดความก้าวหน้าเรียบร้อย!") + st.rerun() + except Exception as e: + logging.error(f"Error loading progress: {str(e)}") + st.sidebar.error("เกิดข้อผิดพลาดในการโหลดไฟล์") - # แสดงคำอธิบายระดับ - st.markdown(f""" -
- 🎓 {level_options[level]['description']} -
📚 เหมาะสำหรับอายุ: {level_options[level]['age_range']} -
- """, unsafe_allow_html=True) - + # Level Selection + st.sidebar.markdown("#### 🎯 ระดับการเรียน") + level = show_level_selection() st.session_state.level = level - if st.button("เริ่มเรื่องใหม่ | Start New Story"): - st.session_state.should_reset = True - st.rerun() - - -# Main content area -col1, col2 = st.columns([3, 1]) - -with col1: - # Story Display Box - st.markdown(""" -
-
📖 เรื่องราวของคุณ
-
Your Story
-
- """, unsafe_allow_html=True) + # Theme Change Option + if st.session_state.current_theme: + st.sidebar.markdown("#### 🎨 ธีมเรื่องราว") + if st.sidebar.button("🔄 เปลี่ยนธีม", use_container_width=True): + st.session_state.current_theme = None + st.session_state.theme_story_starter = None + st.rerun() - story_display = st.container() - - with story_display: - if not st.session_state.story: - st.info("เริ่มต้นผจญภัยด้วยการเขียนประโยคแรกกันเลย! | Start your adventure by writing the first sentence!") + # Reset Option + st.sidebar.markdown("#### 🔄 รีเซ็ตเรื่องราว") + if st.sidebar.button("เริ่มเรื่องใหม่", use_container_width=True): + if st.session_state.story: + if st.sidebar.checkbox("ยืนยันการเริ่มใหม่"): + st.session_state.should_reset = True + st.rerun() else: - for idx, entry in enumerate(st.session_state.story): - if entry['role'] == 'You': - # เพิ่มไอคอนแสดงสถานะความถูกต้อง - status_icon = "✅ " if entry.get('is_correct') else "✍️ " - st.write(f"👤 You: {status_icon}{entry['content']}") - elif entry['role'] == 'AI': - st.write("🤖 AI:", entry['content']) - - # User Input Box + st.sidebar.info("ยังไม่มีเรื่องราวที่จะรีเซ็ต") + +def show_story_input(): + """Display story input section""" st.markdown("""
✏️ ถึงตาคุณแล้ว
@@ -851,144 +1811,402 @@ with col1:
""", unsafe_allow_html=True) - # Text input with callback - st.text_area( + # Initialize clear_input flag if not exists + if 'clear_input' not in st.session_state: + st.session_state.clear_input = False + + # Default value for text input + default_value = "" if st.session_state.clear_input else st.session_state.get('text_input', "") + + # If clear_input flag is True, reset it + if st.session_state.clear_input: + st.session_state.clear_input = False + + # Input area + text_input = st.text_area( "เขียนต่อจากเรื่องราว | Continue the story:", + value=default_value, height=100, - key="text_input", - label_visibility="collapsed" + key="story_input_area", + help="พิมพ์ประโยคภาษาอังกฤษเพื่อต่อเรื่อง", + label_visibility="collapsed" ) - # Submit button with callback - st.button( - "ส่งคำตอบ | Submit", - on_click=submit_story - ) + # Save current input to session state + st.session_state.text_input = text_input + + # Submit button with character count + col1, col2 = st.columns([3, 1]) + with col1: + if st.button("📝 ส่งคำตอบ | Submit", use_container_width=True): + if not text_input.strip(): + st.warning("กรุณาเขียนข้อความก่อนส่ง") + return + + try: + with st.spinner("กำลังวิเคราะห์ประโยค..."): + handle_story_submission(text_input.strip()) + except Exception as e: + logging.error(f"Error submitting story: {str(e)}") + st.error("เกิดข้อผิดพลาดในการส่งคำตอบ กรุณาลองใหม่อีกครั้ง") + + with col2: + char_count = len(text_input) + st.markdown(f""" +
+ {char_count}/200 ตัวอักษร +
+ """, unsafe_allow_html=True) -with col2: - if st.session_state.feedback: +def handle_story_submission(text: str): + """Handle story submission and processing""" + if not st.session_state.story: + st.error("กรุณาเลือกธีมเรื่องราวก่อนเริ่มเขียน") + return + + try: + # Get feedback + feedback_data = provide_feedback(text, st.session_state.level) + st.session_state.feedback = feedback_data + is_correct = not feedback_data.get('has_errors', False) + + # Add user's sentence + st.session_state.story.append({ + "role": "You", + "content": text, + "is_corrected": False, + "is_correct": is_correct, + "timestamp": datetime.now().isoformat() + }) + + # Update vocabulary + words = set(text.lower().split()) + st.session_state.stats['vocabulary_used'].update(words) + + # Update points and achievements + update_points(is_correct) + update_achievements() + + # Generate AI continuation + try: + logging.info("Attempting to generate AI continuation...") + # ใช้ข้อความที่ถูกต้องสำหรับการต่อเรื่อง + text_for_continuation = feedback_data['corrected'] if feedback_data.get('has_errors') else text + + ai_response = generate_story_continuation(text_for_continuation, st.session_state.level) + + # Log the AI response + logging.info(f"AI Response generated: {ai_response}") + + if ai_response and ai_response.strip(): + st.session_state.story.append({ + "role": "AI", + "content": ai_response, + "timestamp": datetime.now().isoformat() + }) + logging.info("AI response added to story successfully") + else: + logging.error("AI generated empty response") + # กรณีที่ AI ไม่สร้างประโยค ให้สร้างประโยคง่ายๆ ตาม theme + fallback_response = generate_fallback_response(st.session_state.current_theme, st.session_state.level) + st.session_state.story.append({ + "role": "AI", + "content": fallback_response, + "timestamp": datetime.now().isoformat() + }) + except Exception as e: + logging.error(f"Error generating AI continuation: {str(e)}") + # สร้าง fallback response เมื่อเกิดข้อผิดพลาด + fallback_response = generate_fallback_response(st.session_state.current_theme, st.session_state.level) + st.session_state.story.append({ + "role": "AI", + "content": fallback_response, + "timestamp": datetime.now().isoformat() + }) + + # Update session stats + update_session_stats() + + # Set flag to clear input on next rerun + st.session_state.clear_input = True + + # Rerun to update UI + st.rerun() + + except Exception as e: + logging.error(f"Error in story submission: {str(e)}") + raise + +def generate_fallback_response(theme_id: str, level: str) -> str: + """Generate a simple fallback response when AI continuation fails""" + try: + theme = story_themes.get(theme_id, {}) + if theme: + # ใช้คำศัพท์จาก theme และ level ที่เลือก + vocab = theme.get('vocabulary', {}).get(level, []) + if vocab: + word = random.choice(vocab) + + # สร้างประโยคตาม level + if level == 'Beginner': + return f"The story continues with {word}..." + elif level == 'Intermediate': + return f"Something interesting happened with the {word}." + else: # Advanced + return f"The mystery deepens as we discover more about the {word}." + + # Default fallback if no theme-specific response can be generated + return "The story continues..." + + except Exception as e: + logging.error(f"Error generating fallback response: {str(e)}") + return "What happens next?" + +def show_main_interface(): + """Display main story interface""" + col1, col2 = st.columns([3, 1]) + + with col1: + # Story display st.markdown("""
-
📝 คำแนะนำจากครู
-
Writing Feedback
+
📖 เรื่องราวของคุณ
+
Your Story
""", unsafe_allow_html=True) - 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) - - # Help and Suggestions Box - st.markdown(""" -
-
✨ เครื่องมือช่วยเขียน
-
Writing Tools
-
- """, unsafe_allow_html=True) - - if st.button("ดูคำศัพท์แนะนำ | Get Vocabulary Ideas"): - vocab_suggestions = get_vocabulary_suggestions() - st.markdown("#### 📚 คำศัพท์น่ารู้ | Useful Words") - for word in vocab_suggestions: - st.markdown(f"• {word}") + show_story() + + if st.session_state.story: + show_story_input() - if st.button("ขอคำใบ้ | Get Creative Prompt"): - prompt = get_creative_prompt() - st.markdown(f""" -
-
💭 {prompt['thai']}
-
💭 {prompt['eng']}
+ with col2: + # Feedback section + show_feedback_section() + + # Writing tools + show_writing_tools() + + # Achievements + with st.expander("🏆 ความสำเร็จ | Achievements"): + show_achievements() + + # Save options + show_save_options() + +# === 6. MAIN APPLICATION LOGIC === +def main(): + """Main application entry point""" + try: + # Initialize states + init_session_state() + init_theme_state() + + # Add watermark + st.markdown(""" +
+ Powered by JoyStory AI
""", unsafe_allow_html=True) - st.markdown("---") # เส้นคั่น - show_achievements() - - # Achievements Box - st.markdown(""" -
-
🏆 ความสำเร็จ
-
Achievements
-
- """, unsafe_allow_html=True) - - # Save Story Button - if st.session_state.story: - st.markdown("### 💾 บันทึกเรื่องราว") - col1, col2 = st.columns(2) + # Show header + st.markdown("# 📖 JoyStory") + show_welcome_section() + show_parent_guide() + + # Sidebar + with st.sidebar: + show_sidebar() + + # Session Status Check + check_session_status() + + # Main content area + main_container = st.container() + with main_container: + if not st.session_state.current_theme: + show_theme_selection() + else: + show_main_interface() + + # Handle reset if needed + if st.session_state.should_reset: + reset_story() + + # Auto-save progress periodically + if st.session_state.story: + auto_save_progress() + + except Exception as e: + handle_application_error(e) + +def check_session_status(): + """Check and maintain session status""" + try: + # Update session duration + if 'session_start' not in st.session_state: + st.session_state.session_start = datetime.now() - with col1: - # บันทึกเป็น PDF - pdf = create_story_pdf() - st.download_button( - label="📑 ดาวน์โหลดเป���น PDF", - data=pdf, - file_name=f"my_story_{datetime.now().strftime('%Y%m%d')}.pdf", - mime="application/pdf" - ) + # Check for session timeout (2 hours) + session_duration = (datetime.now() - st.session_state.session_start).total_seconds() + if session_duration > 7200: # 2 hours + st.warning("เซสชันหมดอายุ กรุณาบันทึกความก้าวหน้าและรีเฟรชหน้าเว็บ") - with col2: - # บันทึกข้อมูลดิบ (JSON) สำหรับโหลดกลับมาเล่นต่อ - story_data = { - 'level': st.session_state.level, - 'date': datetime.now().isoformat(), - 'story': st.session_state.story, - 'achievements': st.session_state.achievements, - 'points': st.session_state.points, - 'stats': { - 'total_sentences': st.session_state.stats['total_sentences'], - 'correct_first_try': st.session_state.stats['correct_first_try'], - 'accuracy_rate': st.session_state.stats['accuracy_rate'], - 'vocabulary_used': list(st.session_state.stats['vocabulary_used']) + # Check for inactivity (30 minutes) + last_interaction = datetime.fromisoformat(st.session_state.last_interaction) + inactivity_duration = (datetime.now() - last_interaction).total_seconds() + if inactivity_duration > 1800: # 30 minutes + st.info("ไม่มีกิจกรรมเป็นเวลานาน กรุณาบันทึกความก้าวหน้าเพื่อความปลอดภัย") + + # Update stats if story exists + if st.session_state.story: + update_session_stats() + + except Exception as e: + logging.error(f"Error checking session status: {str(e)}") + +def auto_save_progress(): + """Automatically save progress to session state""" + try: + current_progress = { + 'timestamp': datetime.now().isoformat(), + 'story': st.session_state.story, + 'stats': st.session_state.stats, + 'points': st.session_state.points, + 'achievements': st.session_state.achievements + } + + # Save to session state + if 'auto_save' not in st.session_state: + st.session_state.auto_save = {} + + st.session_state.auto_save = current_progress + + # Add timestamp for last auto-save + st.session_state.last_auto_save = datetime.now().isoformat() + + except Exception as e: + logging.error(f"Error in auto-save: {str(e)}") + +def handle_application_error(error: Exception): + """Handle application-wide errors""" + logging.error(f"Application error: {str(error)}") + + error_message = """ +
+

+ ⚠️ เกิดข้อผิดพลาดในระบบ +

+

+ กรุณาลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบ +

+
+ """ + + st.markdown(error_message, unsafe_allow_html=True) + + # Show technical details in expander + with st.expander("รายละเอียดข้อผิดพลาด (สำหรับผู้ดูแลระบบ)"): + st.code(f""" + Error Type: {type(error).__name__} + Error Message: {str(error)} + Timestamp: {datetime.now().isoformat()} + """) + +def show_debug_info(): + """Show debug information (development only)""" + if st.session_state.get('debug_mode'): + with st.expander("🔧 Debug Information"): + st.json({ + 'session_state': { + key: str(value) if isinstance(value, (set, datetime)) else value + for key, value in st.session_state.items() + if key not in ['client', '_client'] } - } - st.download_button( - label="💾 บันทึกความก้าวหน้า", - data=json.dumps(story_data, indent=2), - file_name=f"story_progress_{datetime.now().strftime('%Y%m%d')}.json", - mime="application/json" - ) \ No newline at end of file + }) + +# Add CSS for loading animation +st.markdown(""" + +""", unsafe_allow_html=True) + +if __name__ == "__main__": + try: + main() + except Exception as e: + handle_application_error(e) \ No newline at end of file