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("""
-
- """, unsafe_allow_html=True)
-
-def show_parent_guide():
- with st.expander("📖 คำแนะนำสำหรับผู้ปกครอง | Parent's Guide"):
- st.markdown("""
-
-
คำแนะนำในการใช้งาน
-
- - แนะนำให้นั่งเขียนเรื่องราวร่วมกับน้องๆ
- - ช่วยอธิบายคำแนะนำและคำศัพท์ที่น้องๆ ไม่เข้าใจ
- - ให้กำลังใจและชื่นชมเมื่อน้องๆ เขียนได้ดี
- - ใช้เวลาในการเขียนแต่ละครั้งไม่เกิน 20-30 นาที
-
-
💡 เคล็ดลับ: ให้น้องๆ พูดเรื่องราวที่อยากเขียนเป็นภาษาไทยก่อน
- แล้วค่อยๆ ช่วยกันแปลงเป็นภาษาอังกฤษ
-
- """, 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("""
+
+ """, unsafe_allow_html=True)
+
+def show_parent_guide():
+ """Display guide for parents"""
+ with st.expander("📖 คำแนะนำสำหรับผู้ปกครอง | Parent's Guide"):
+ st.markdown("""
+
+
คำแนะนำในการใช้งาน
+
+ -
+ 👥 การมีส่วนร่วม: แนะนำให้นั่งเขียนเรื่องราวร่วมกับน้องๆ
+
+ -
+ 💡 การช่วยเหลือ: ช่วยอธิบายคำแนะนำและคำศัพท์ที่น้องๆ ไม่เข้าใจ
+
+ -
+ 🌟 การให้กำลังใจ: ให้กำลังใจและชื่นชมเมื่อน้องๆ เขียนได้ดี
+
+ -
+ ⏱️ เวลาที่เหมาะสม: ใช้เวลาในการเขียนแต่ละครั้งไม่เกิน 20-30 นาที
+
+
+
+
+ 💡 เคล็ดลับ: ให้น้องๆ พูดเรื่องราวที่อยากเขียนเป็นภาษาไทยก่อน
+ แล้วค่อยๆ ช่วยกันแปลงเป็นภาษาอังกฤษ
+
+
+
+ """, 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']}
+
+
+
✨ คุณลักษณะ:
+
+ {' '.join([f'- • {feature}
' for feature in level_options[level]['features']])}
+
+
+
+ """, 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