Rathapoom commited on
Commit
e37e308
·
verified ·
1 Parent(s): 93d04c0

Create appbackup.app

Browse files
Files changed (1) hide show
  1. appbackup.app +994 -0
appbackup.app ADDED
@@ -0,0 +1,994 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import json
3
+ import datetime
4
+ from openai import OpenAI
5
+ from typing import Dict, List, Set
6
+ import io
7
+ from reportlab.lib import colors
8
+ from reportlab.lib.pagesizes import A4
9
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
10
+ from reportlab.lib.units import inch
11
+ from reportlab.pdfgen import canvas
12
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image
13
+ from reportlab.pdfbase import pdfmetrics
14
+ from reportlab.pdfbase.ttfonts import TTFont
15
+ from datetime import datetime
16
+
17
+ # ระบบ Achievements
18
+ achievements_list = {
19
+ 'perfect_writer': {
20
+ 'name': '🌟 นักเขียนไร้ที่ติ',
21
+ 'description': 'เขียนถูกต้อง 5 ประโยคติดต่อกัน',
22
+ 'condition': lambda: st.session_state.points['streak'] >= 5
23
+ },
24
+ 'vocabulary_master': {
25
+ 'name': '📚 ราชาคำศัพท์',
26
+ 'description': 'ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ',
27
+ 'condition': lambda: len(st.session_state.stats['vocabulary_used']) >= 50
28
+ },
29
+ 'quick_learner': {
30
+ 'name': '🚀 นักเรียนจอมขยัน',
31
+ 'description': 'แก้ไขประโยคให้ถูกต้องภายใน 3 วินาที',
32
+ 'condition': lambda: True # ต้องเพิ่มการจับเวลา
33
+ },
34
+ 'story_master': {
35
+ 'name': '📖 นักแต่งนิทาน',
36
+ 'description': 'เขียนเรื่องยาว 10 ประโยค',
37
+ 'condition': lambda: len(st.session_state.story) >= 10
38
+ },
39
+ 'accuracy_king': {
40
+ 'name': '👑 ราชาความแม่นยำ',
41
+ 'description': 'มีอัตราความถูกต้อง 80% ขึ้นไป (อย่างน้อย 10 ประโยค)',
42
+ 'condition': lambda: (
43
+ st.session_state.stats['total_sentences'] >= 10 and
44
+ st.session_state.stats['accuracy_rate'] >= 80
45
+ )
46
+ }
47
+ }
48
+
49
+ # Set up Streamlit page configuration
50
+ st.set_page_config(
51
+ page_title="JoyStory - Interactive Story Adventure",
52
+ page_icon="📖",
53
+ layout="wide",
54
+ initial_sidebar_state="collapsed",
55
+ )
56
+
57
+ # Initialize OpenAI client
58
+ client = OpenAI()
59
+
60
+ # Define level configurations
61
+ level_options = {
62
+ 'Beginner': {
63
+ 'thai_name': 'ระดับเริ่มต้น (ป.1-3)',
64
+ 'age_range': '7-9 ปี',
65
+ 'description': 'เหมาะสำหรับน้องๆ ที่เริ่มเรียนรู้การเขียนประโยคภาษาอังกฤษ',
66
+ 'features': [
67
+ 'ประโยคสั้นๆ ง่ายๆ',
68
+ 'คำศัพท์พื้นฐานที่ใช้ในชีวิตประจำวัน',
69
+ 'มีคำแนะนำภาษาไทยละเอียด',
70
+ 'เน้นการใช้ Present Simple Tense'
71
+ ]
72
+ },
73
+ 'Intermediate': {
74
+ 'thai_name': 'ระดับกลาง (ป.4-6)',
75
+ 'age_range': '10-12 ปี',
76
+ 'description': 'เหมาะสำหรับน้องๆ ที่สามารถเขียนประโยคพื้นฐานได้แล้ว',
77
+ 'features': [
78
+ 'ประโยคซับซ้อนขึ้น',
79
+ 'เริ่มใช้ Past Tense ได้',
80
+ 'คำศัพท์หลากหลายขึ้น',
81
+ 'สามารถเขียนเรื่องราวต่อเนื่องได้'
82
+ ]
83
+ },
84
+ 'Advanced': {
85
+ 'thai_name': 'ระดับก้าวหน้า (ม.1-3)',
86
+ 'age_range': '13-15 ปี',
87
+ 'description': 'เหมาะสำหรับน้องๆ ที่มีพื้นฐานภาษาอังกฤษดี',
88
+ 'features': [
89
+ 'เขียนเรื่องราวได้หลากหลายรูปแบบ',
90
+ 'ใช้ Tense ต่างๆ ได้',
91
+ 'คำศัพท์ระดับสูงขึ้น',
92
+ 'สามารถแต่งเรื่องที่ซับซ้อนได้'
93
+ ]
94
+ }
95
+ }
96
+
97
+ # Add custom CSS including new styles for level selection
98
+ st.markdown("""
99
+ <style>
100
+ @import url('https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap');
101
+
102
+ .thai-eng {
103
+ font-size: 1.1em;
104
+ padding: 10px;
105
+ background-color: #f8f9fa;
106
+ border-radius: 8px;
107
+ margin: 10px 0;
108
+ }
109
+ .thai {
110
+ color: #1e88e5;
111
+ font-family: 'Sarabun', sans-serif;
112
+ }
113
+ .eng {
114
+ color: #333;
115
+ }
116
+ .level-info {
117
+ background-color: #e3f2fd;
118
+ padding: 10px;
119
+ border-radius: 8px;
120
+ margin: 10px 0;
121
+ font-size: 0.9em;
122
+ }
123
+ .parent-guide {
124
+ background-color: #fff3e0;
125
+ padding: 15px;
126
+ border-radius: 8px;
127
+ margin: 15px 0;
128
+ border-left: 4px solid #ff9800;
129
+ }
130
+ </style>
131
+ """, unsafe_allow_html=True)
132
+
133
+ # Initialize session state variables
134
+ def init_session_state():
135
+ if 'story' not in st.session_state:
136
+ st.session_state.story = []
137
+ if 'feedback' not in st.session_state:
138
+ st.session_state.feedback = None
139
+ if 'level' not in st.session_state:
140
+ st.session_state.level = 'Beginner'
141
+ if 'unique_words' not in st.session_state:
142
+ st.session_state.unique_words = set()
143
+ if 'total_words' not in st.session_state:
144
+ st.session_state.total_words = 0
145
+ if 'should_reset' not in st.session_state:
146
+ st.session_state.should_reset = False
147
+ if 'user_input' not in st.session_state:
148
+ st.session_state.user_input = ""
149
+ if 'points' not in st.session_state:
150
+ st.session_state.points = {
151
+ 'total': 0,
152
+ 'perfect_sentences': 0,
153
+ 'corrections_made': 0,
154
+ 'streak': 0,
155
+ 'max_streak': 0
156
+ }
157
+ if 'stats' not in st.session_state:
158
+ st.session_state.stats = {
159
+ 'total_sentences': 0,
160
+ 'correct_first_try': 0,
161
+ 'accuracy_rate': 0.0,
162
+ 'vocabulary_used': set()
163
+ }
164
+ if 'achievements' not in st.session_state:
165
+ st.session_state.achievements = []
166
+
167
+ init_session_state()
168
+
169
+ # เพิ่มฟังก์ชันสำหรับจัดการ input
170
+ def clear_input():
171
+ st.session_state.user_input = ""
172
+
173
+ # Callback function for submit button
174
+ def submit_story():
175
+ if st.session_state.text_input.strip():
176
+ user_text = st.session_state.text_input
177
+
178
+ # เพิ่มคำศัพท์ที่ใช้
179
+ words = set(user_text.lower().split())
180
+ st.session_state.stats['vocabulary_used'].update(words)
181
+
182
+ # ตรวจสอบความถูกต้อง
183
+ feedback_data = provide_feedback(user_text, st.session_state.level)
184
+ is_correct = not feedback_data.get('has_errors', False)
185
+
186
+ # อัพเดตคะแนน
187
+ update_points(is_correct)
188
+
189
+ # อัพเดต achievements
190
+ update_achievements()
191
+
192
+ # เพิ่มประโยคของผู้ใช้
193
+ story_index = len(st.session_state.story)
194
+ st.session_state.story.append({
195
+ "role": "You",
196
+ "content": user_text,
197
+ "is_corrected": False
198
+ })
199
+
200
+ try:
201
+ # รับ feedback และประโยคที่ถูกต้อง
202
+ feedback_data = provide_feedback(user_text, st.session_state.level)
203
+ st.session_state.feedback = feedback_data
204
+
205
+ # ถ้ามีข้อผิดพลาด แสดงคำแนะนำ
206
+ if feedback_data['has_errors']:
207
+ st.markdown(f"""
208
+ <div style='background-color: #fff3e0; padding: 10px; border-radius: 5px; margin: 10px 0;'>
209
+ <p style='color: #ff6d00;'>🎯 คำแนะนำ:</p>
210
+ <p>{feedback_data['feedback']}</p>
211
+ </div>
212
+ """, unsafe_allow_html=True)
213
+
214
+ # Generate AI continuation using corrected text if there were errors
215
+ ai_response = generate_story_continuation(
216
+ feedback_data['corrected'] if feedback_data['has_errors'] else user_text,
217
+ st.session_state.level
218
+ )
219
+ st.session_state.story.append({"role": "AI", "content": ai_response})
220
+
221
+ except Exception as e:
222
+ st.error("เกิดข้อผิดพลาดในการวิเคราะห์ประโยค กรุณาลองใหม่อีกครั้ง")
223
+ # Log error for debugging
224
+ st.error(f"Debug - Error in submit_story: {str(e)}")
225
+
226
+ # Clear input
227
+ st.session_state.text_input = ""
228
+
229
+ def show_welcome_section():
230
+ st.markdown("""
231
+ <div class="welcome-header">
232
+ <div class="thai">
233
+ 🌟 ยินดีต้อนรับสู่ JoyStory - แอพฝึกเขียนภาษาอังกฤษแสนสนุก!
234
+ <br>
235
+ เรียนรู้ภาษาอังกฤษผ่านการเขียนเรื่องราวด้วยตัวเอง พร้อมผู้ช่วย AI ที่จะช่วยแนะนำและให้กำลังใจ
236
+ </div>
237
+ <div class="eng">
238
+ Welcome to JoyStory - Fun English Writing Adventure!
239
+ </div>
240
+ </div>
241
+ """, unsafe_allow_html=True)
242
+
243
+ def show_parent_guide():
244
+ with st.expander("📖 คำแนะนำสำหรับผู้ปกครอง | Parent's Guide"):
245
+ st.markdown("""
246
+ <div class="parent-guide">
247
+ <h4>คำแนะนำในการใช้งาน</h4>
248
+ <ul>
249
+ <li>แนะนำให้นั่งเขียนเรื่องราวร่วมกับน้องๆ</li>
250
+ <li>ช่วยอธิบายคำแนะนำและคำศัพท์ที่น้องๆ ไม่เข้าใจ</li>
251
+ <li>ให้กำลังใจและชื่นชมเมื่อน้องๆ เขียนได้ดี</li>
252
+ <li>ใช้เวลาในการเขียนแต่ละครั้งไม่เกิน 20-30 นาที</li>
253
+ </ul>
254
+ <p>💡 เคล็ดลับ: ให้น้องๆ พูดเรื่องราวที่อยากเขียนเป็นภาษาไทยก่อน
255
+ แล้วค่อยๆ ช่วยกันแปลงเป็นภาษาอังกฤษ</p>
256
+ </div>
257
+ """, unsafe_allow_html=True)
258
+
259
+ def generate_story_continuation(user_input: str, level: str) -> str:
260
+ """Generate AI story continuation using ChatGPT with level-appropriate content."""
261
+ level_context = {
262
+ 'Beginner': """
263
+ Role: You are a teaching assistant for Thai students in grades 1-3.
264
+ Rules:
265
+ - Use only 1-2 VERY simple sentences
266
+ - Use Present Simple Tense only
267
+ - Use basic vocabulary (family, school, daily activities)
268
+ - Each sentence should be 5-7 words maximum
269
+ - Focus on clear, basic responses
270
+ Example responses:
271
+ - "The cat sits under the tree."
272
+ - "The boy plays with his toy car."
273
+ - "They walk to school together."
274
+ """,
275
+ 'Intermediate': """
276
+ Role: You are a teaching assistant for Thai students in grades 4-6.
277
+ Rules:
278
+ - Use exactly 2 sentences maximum
279
+ - Can use Present or Past Tense
280
+ - Keep each sentence under 12 words
281
+ - Use grade-appropriate vocabulary
282
+ - Add simple descriptions but stay concise
283
+ Example responses:
284
+ - "The brown cat jumped over the tall wooden fence. It landed softly in the garden."
285
+ - "Tom carefully opened his mysterious new book. The colorful pages showed amazing magical creatures."
286
+ """,
287
+ 'Advanced': """
288
+ Role: You are a teaching assistant for Thai students in grades 7-9.
289
+ Rules:
290
+ - Use 2-3 sentences maximum (no more!)
291
+ - Various tenses are allowed
292
+ - No strict word limit per sentence, but keep overall response concise
293
+ - Use more sophisticated vocabulary and sentence structures
294
+ - Create engaging responses that encourage creative continuation
295
+ - Focus on quality and natural flow rather than sentence length
296
+ Example responses:
297
+ - "Sarah discovered an ancient-looking letter hidden beneath the creaky floorboards. The yellowed paper contained a mysterious message."
298
+ - "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."
299
+ """
300
+ }
301
+
302
+ try:
303
+ story_context = '\n'.join([entry['content'] for entry in st.session_state.story[-3:]]) # Only use last 3 entries
304
+
305
+ response = client.chat.completions.create(
306
+ model="gpt-4o-mini",
307
+ messages=[
308
+ {"role": "system", "content": f"""You are a storytelling assistant for Thai students.
309
+ {level_context[level]}
310
+ CRUCIAL GUIDELINES:
311
+ - NEVER exceed the maximum number of sentences for the level
312
+ - Create openings for student's creativity
313
+ - Do not resolve plot points or conclude the story
314
+ - Avoid using 'suddenly' or 'then'
315
+ - Make each sentence meaningful but incomplete
316
+ - Leave room for the student to develop the story
317
+
318
+ Remember: This is interactive storytelling - let the student drive the story forward."""},
319
+ {"role": "user", "content": f"Story context (recent):\n{story_context}\nStudent's input:\n{user_input}\nProvide a brief continuation:"}
320
+ ],
321
+ max_tokens={
322
+ 'Beginner': 30,
323
+ 'Intermediate': 40,
324
+ 'Advanced': 50
325
+ }[level],
326
+ temperature=0.7,
327
+ presence_penalty=0.6, # Discourage repetitive responses
328
+ frequency_penalty=0.6 # Encourage diversity in responses
329
+ )
330
+
331
+ # Additional length check and cleanup
332
+ response_text = response.choices[0].message.content.strip()
333
+ sentences = [s.strip() for s in response_text.split('.') if s.strip()]
334
+
335
+ # Limit sentences based on level
336
+ max_sentences = {'Beginner': 2, 'Intermediate': 2, 'Advanced': 3}
337
+ if len(sentences) > max_sentences[level]:
338
+ sentences = sentences[:max_sentences[level]]
339
+
340
+ # Reconstruct response with proper punctuation
341
+ response_text = '. '.join(sentences) + '.'
342
+
343
+ return response_text
344
+
345
+ except Exception as e:
346
+ st.error(f"Error generating story continuation: {str(e)}")
347
+ return "I'm having trouble continuing the story. Please try again."
348
+
349
+ # ฟังก์ชันสำหรับแก้ไขประโยค
350
+ def apply_correction(story_index: int, corrected_text: str):
351
+ """Apply correction to a specific story entry."""
352
+ if 0 <= story_index < len(st.session_state.story):
353
+ original_text = st.session_state.story[story_index]['content']
354
+ # เก็บประวัติการแก้ไข
355
+ if 'corrections' not in st.session_state:
356
+ st.session_state.corrections = {}
357
+
358
+ st.session_state.corrections[story_index] = {
359
+ 'original': original_text,
360
+ 'corrected': corrected_text,
361
+ 'timestamp': datetime.datetime.now().isoformat()
362
+ }
363
+ # แก้ไขประโยคในเรื่อง
364
+ st.session_state.story[story_index]['content'] = corrected_text
365
+ st.session_state.story[story_index]['is_corrected'] = True
366
+
367
+ # แสดงข้อความยืนยันการแก้ไข
368
+ st.success("✅ แก้ไขประโยคเรียบร้อยแล้ว!")
369
+
370
+ def get_vocabulary_suggestions() -> List[str]:
371
+ """Get contextual vocabulary suggestions with Thai translations."""
372
+ try:
373
+ recent_story = '\n'.join([entry['content'] for entry in st.session_state.story[-3:]] if st.session_state.story else "Story just started")
374
+
375
+ response = client.chat.completions.create(
376
+ model="gpt-4o-mini",
377
+ messages=[
378
+ {"role": "system", "content": f"""You are a Thai-English bilingual teacher.
379
+ Suggest 5 English words with their Thai translations and examples.
380
+ Format each suggestion as:
381
+ word (type) - คำแปล | example sentence
382
+ Make sure words match {st.session_state.level} level."""},
383
+ {"role": "user", "content": f"Story context:\n{recent_story}\n\nSuggest 5 relevant words with Thai translations:"}
384
+ ],
385
+ max_tokens=200,
386
+ temperature=0.8
387
+ )
388
+ return response.choices[0].message.content.split('\n')
389
+ except Exception as e:
390
+ st.error(f"Error getting vocabulary suggestions: {str(e)}")
391
+ return ["happy (adj) - มีความสุข | I am happy today",
392
+ "run (verb) - วิ่ง | The dog runs fast",
393
+ "tree (noun) - ต้นไม้ | A tall tree"]
394
+
395
+ # In the main UI section, update how vocabulary suggestions are displayed:
396
+ if st.button("Get Vocabulary Ideas"):
397
+ vocab_suggestions = get_vocabulary_suggestions()
398
+ st.markdown("#### 📚 Suggested Words")
399
+ for word in vocab_suggestions:
400
+ st.markdown(f"• *{word}*")
401
+
402
+ # And update how feedback is displayed to be more concise:
403
+ if st.session_state.feedback:
404
+ st.markdown("""
405
+ <div style='background-color: #f0f2f6; padding: 8px; border-radius: 4px; margin-bottom: 10px;'>
406
+ 📝 <i>{}</i>
407
+ </div>
408
+ """.format(st.session_state.feedback), unsafe_allow_html=True)
409
+
410
+ # Update the creative prompt function
411
+ def get_creative_prompt() -> Dict[str, str]:
412
+ """Generate a short, simple bilingual creative prompt."""
413
+ try:
414
+ response = client.chat.completions.create(
415
+ model="gpt-4o-mini",
416
+ messages=[
417
+ {"role": "system", "content": """Create very short story prompts in both English and Thai.
418
+ Keep it simple and under 6 words each.
419
+ Example formats:
420
+ - "What did the cat find?"
421
+ - "Where did they go next?"
422
+ - "How does the story end?"
423
+ """},
424
+ {"role": "user", "content": "Generate a simple, short story prompt:"}
425
+ ],
426
+ max_tokens=50,
427
+ temperature=0.7
428
+ )
429
+ prompt_eng = response.choices[0].message.content
430
+
431
+ # Get Thai translation
432
+ response_thai = client.chat.completions.create(
433
+ model="gpt-4o-mini",
434
+ messages=[
435
+ {"role": "system", "content": "Translate to short Thai prompt, keep it simple and natural:"},
436
+ {"role": "user", "content": prompt_eng}
437
+ ],
438
+ max_tokens=50,
439
+ temperature=0.7
440
+ )
441
+ prompt_thai = response_thai.choices[0].message.content
442
+
443
+ return {"eng": prompt_eng, "thai": prompt_thai}
444
+ except Exception as e:
445
+ st.error(f"Error generating creative prompt: {str(e)}")
446
+ return {
447
+ "eng": "What happens next?",
448
+ "thai": "แล้วอะไรจะเกิดขึ้นต่อ?"
449
+ }
450
+
451
+ def provide_feedback(text: str, level: str) -> Dict[str, str]:
452
+ """Provide feedback and corrected sentence."""
453
+ try:
454
+ response = client.chat.completions.create(
455
+ model="gpt-4o-mini",
456
+ messages=[
457
+ {"role": "system", "content": f"""You are a Thai English teacher helping {level} students.
458
+ Your task is to review the student's sentence and provide feedback.
459
+
460
+ Return your response in this EXACT format only (must be valid JSON):
461
+ {{
462
+ "feedback": "(ข้อเสนอแนะภาษาไทย)",
463
+ "corrected": "(ประโยคภาษาอังกฤษที่ถูกต้อง)",
464
+ "has_errors": true/false
465
+ }}
466
+
467
+ Example 1 - with error:
468
+ {{
469
+ "feedback": "คำว่า 'go' เมื่อพูดถึงเหตุการณ์ในอดีต ต้องเปลี่ยนเป็น 'went'",
470
+ "corrected": "I went to school yesterday.",
471
+ "has_errors": true
472
+ }}
473
+
474
+ Example 2 - no error:
475
+ {{
476
+ "feedback": "เขียนได้ถูกต้องแล้วค่ะ ประโยคสื่อความหมายได้ดี",
477
+ "corrected": "The cat is sleeping.",
478
+ "has_errors": false
479
+ }}"""},
480
+ {"role": "user", "content": f"Review this sentence and provide feedback in the specified JSON format: {text}"}
481
+ ],
482
+ max_tokens=200,
483
+ temperature=0.3
484
+ )
485
+
486
+ # Get the response text
487
+ response_text = response.choices[0].message.content.strip()
488
+
489
+ try:
490
+ # แสดง debug info เพื่อตรวจสอบ response
491
+ st.write("Debug - Response received:", response_text)
492
+
493
+ # Parse JSON response
494
+ feedback_data = json.loads(response_text)
495
+
496
+ # Validate required fields
497
+ if all(key in feedback_data for key in ['feedback', 'corrected', 'has_errors']):
498
+ return feedback_data
499
+ else:
500
+ raise ValueError("Missing required fields in response")
501
+
502
+ except json.JSONDecodeError as json_err:
503
+ st.error(f"Debug - JSON Error: {str(json_err)}")
504
+ raise
505
+
506
+ except Exception as e:
507
+ st.error(f"Debug - Error: {str(e)}")
508
+ return {
509
+ "feedback": "⚠️ ระบบไม่สามารถวิเคราะห์ประโยคได้ กรุณาลองใหม่อีกครั้ง",
510
+ "corrected": text,
511
+ "has_errors": False
512
+ }
513
+
514
+ def update_points(is_correct_first_try: bool):
515
+ """อัพเดตคะแนนตามผลการเขียน"""
516
+ base_points = 10
517
+ if is_correct_first_try:
518
+ points = base_points * 2
519
+ st.session_state.points['perfect_sentences'] += 1
520
+ st.session_state.points['streak'] += 1
521
+ if st.session_state.points['streak'] > st.session_state.points['max_streak']:
522
+ st.session_state.points['max_streak'] = st.session_state.points['streak']
523
+ else:
524
+ points = base_points // 2
525
+ st.session_state.points['corrections_made'] += 1
526
+ st.session_state.points['streak'] = 0
527
+
528
+ st.session_state.points['total'] += points
529
+
530
+ st.session_state.stats['total_sentences'] += 1
531
+ if is_correct_first_try:
532
+ st.session_state.stats['correct_first_try'] += 1
533
+ st.session_state.stats['accuracy_rate'] = (
534
+ st.session_state.stats['correct_first_try'] /
535
+ st.session_state.stats['total_sentences'] * 100
536
+ )
537
+
538
+ # แสดงผลคะแนนและความสำเร็จ
539
+ def show_achievements():
540
+ """แสดงความสำเร็จและสถิติ"""
541
+ with st.container():
542
+ # 1. แสดงคะแนนรวมและ Streak
543
+ st.markdown(f"""
544
+ <div style='background-color: #f0f8ff; padding: 15px; border-radius: 10px; margin-bottom: 15px;'>
545
+ <h3 style='color: #1e88e5; margin: 0;'>🌟 คะแนนรวม: {st.session_state.points['total']}</h3>
546
+ <p style='margin: 5px 0;'>Streak ปัจจุบัน: {st.session_state.points['streak']} ประโยค</p>
547
+ <p style='margin: 5px 0;'>Streak สูงสุด: {st.session_state.points['max_streak']} ประโยค</p>
548
+ </div>
549
+ """, unsafe_allow_html=True)
550
+
551
+ # 2. แสดงสถิติการเขียน
552
+ st.markdown("""
553
+ <div style='margin-bottom: 10px;'>
554
+ <h3>📊 สถิติการเขียน</h3>
555
+ </div>
556
+ """, unsafe_allow_html=True)
557
+
558
+ col1, col2 = st.columns(2)
559
+ with col1:
560
+ st.metric(
561
+ "ประโยคที่เขียนทั้งหมด",
562
+ st.session_state.stats['total_sentences'],
563
+ help="จำนวนประโยคทั้งหมดที่คุณได้เขียน"
564
+ )
565
+ st.metric(
566
+ "ถูกต้องตั้งแต่แรก",
567
+ st.session_state.stats['correct_first_try'],
568
+ help="จำนวนประโยคที่ถูกต้องโดยไม่ต้องแก้ไข"
569
+ )
570
+ with col2:
571
+ st.metric(
572
+ "ความแม่นยำ",
573
+ f"{st.session_state.stats['accuracy_rate']:.1f}%",
574
+ help="เปอร์เซ็นต์ของประโยคที่ถูกต้องตั้งแต่แรก"
575
+ )
576
+ st.metric(
577
+ "คำศัพท์ที่ใช้",
578
+ len(st.session_state.stats['vocabulary_used']),
579
+ help="จำนวนคำศัพท์ที่ไม่ซ้ำกันที่คุณได้ใช้"
580
+ )
581
+
582
+ # 3. แสดงความสำเร็จ
583
+ st.markdown("""
584
+ <div style='margin: 20px 0 10px 0;'>
585
+ <h3>🏆 ความสำเร็จ</h3>
586
+ </div>
587
+ """, unsafe_allow_html=True)
588
+
589
+ if st.session_state.achievements:
590
+ for achievement in st.session_state.achievements:
591
+ st.success(achievement)
592
+ else:
593
+ st.info("ยังไม่มีความสำเร็จ - เขียนต่อไปเพื่อปลดล็อกรางวัล!")
594
+
595
+ # 4. แสดงความสำเร็จที่ยังไม่ได้รับ (ตัวเลือก)
596
+ if not st.session_state.achievements: # ถ้ายังไม่มีความสำเร็จ
597
+ st.markdown("""
598
+ <div style='margin-top: 15px; font-size: 0.9em; color: #666;'>
599
+ เป้าหมายที่จะได้รับความสำเร็จ:
600
+ </div>
601
+ """, unsafe_allow_html=True)
602
+ st.markdown("""
603
+ - 🌟 นักเขียนไร้ที่ติ: เขียนถูกต้อง 5 ประโยคติดต่อกัน
604
+ - 📚 ราชาคำศัพท์: ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ
605
+ - 📖 นักแต่งนิทาน: เขียนเรื่องยาว 10 ประโยค
606
+ - 👑 ราชาความแม่นยำ: มีอัตราความถูกต้อง 80% ขึ้นไป
607
+ """)
608
+
609
+ def update_achievements():
610
+ """ตรวจสอบและอัพเดตความสำเร็จ"""
611
+ current_achievements = st.session_state.achievements
612
+
613
+ # เช็คเงื่อนไขต่างๆ
614
+ if st.session_state.points['streak'] >= 5 and "🌟 นักเขียนไร้ที่ติ" not in current_achievements:
615
+ current_achievements.append("🌟 นักเขียนไร้ที่ติ")
616
+ st.success("🎉 ได้รับความสำเร็จใหม่: นักเขียนไร้ที่ติ!")
617
+
618
+ if len(st.session_state.stats['vocabulary_used']) >= 50 and "📚 ราชาคำศัพท์" not in current_achievements:
619
+ current_achievements.append("📚 ราชาคำศัพท์")
620
+ st.success("🎉 ได้รับความสำเร็จใหม่: ราชาคำศัพท์!")
621
+
622
+ if len(st.session_state.story) >= 10 and "📖 นักแต่งนิทาน" not in current_achievements:
623
+ current_achievements.append("📖 นักแต่งนิทาน")
624
+ st.success("🎉 ได้รับความสำเร็จใหม่: นักแต่งนิทาน!")
625
+
626
+ if (st.session_state.stats['total_sentences'] >= 10 and
627
+ st.session_state.stats['accuracy_rate'] >= 80 and
628
+ "👑 ราชาความแม่นยำ" not in current_achievements):
629
+ current_achievements.append("👑 ราชาความแม่นยำ")
630
+ st.success("🎉 ได้รับความสำเร็จใหม่: ราชาความแม่นยำ!")
631
+
632
+ def create_story_pdf():
633
+ """สร้าง PDF จากเรื่อ��ราวที่เขียน"""
634
+ buffer = io.BytesIO()
635
+ doc = SimpleDocTemplate(
636
+ buffer,
637
+ pagesize=A4,
638
+ rightMargin=72,
639
+ leftMargin=72,
640
+ topMargin=72,
641
+ bottomMargin=72
642
+ )
643
+
644
+ # สร้าง styles สำหรับ PDF
645
+ styles = getSampleStyleSheet()
646
+ title_style = ParagraphStyle(
647
+ 'CustomTitle',
648
+ parent=styles['Heading1'],
649
+ fontSize=24,
650
+ spaceAfter=30,
651
+ alignment=1 # center
652
+ )
653
+
654
+ story_style = ParagraphStyle(
655
+ 'StoryText',
656
+ parent=styles['Normal'],
657
+ fontSize=12,
658
+ spaceBefore=12,
659
+ spaceAfter=12,
660
+ leading=16
661
+ )
662
+
663
+ stats_style = ParagraphStyle(
664
+ 'Stats',
665
+ parent=styles['Normal'],
666
+ fontSize=10,
667
+ textColor=colors.gray,
668
+ alignment=1
669
+ )
670
+
671
+ # สร้างเนื้อหา PDF
672
+ elements = []
673
+
674
+ # หน้าปก
675
+ elements.append(Paragraph("My English Story Adventure", title_style))
676
+ elements.append(Paragraph(
677
+ f"Created by: {st.session_state.get('player_name', 'Young Writer')}<br/>"
678
+ f"Date: {datetime.now().strftime('%B %d, %Y')}<br/>"
679
+ f"Level: {st.session_state.level}",
680
+ stats_style
681
+ ))
682
+ elements.append(Spacer(1, 30))
683
+
684
+ # เพิ่มสถิติการเขียน
685
+ stats_text = (
686
+ f"Total Points: {st.session_state.points['total']}<br/>"
687
+ f"Perfect Sentences: {st.session_state.points['perfect_sentences']}<br/>"
688
+ f"Accuracy Rate: {st.session_state.stats['accuracy_rate']:.1f}%<br/>"
689
+ f"Unique Words Used: {len(st.session_state.stats['vocabulary_used'])}"
690
+ )
691
+ elements.append(Paragraph(stats_text, stats_style))
692
+ elements.append(Spacer(1, 30))
693
+
694
+ # เพิ่มเนื้อเรื่อง
695
+ elements.append(Paragraph("The Story", styles['Heading2']))
696
+ for entry in st.session_state.story:
697
+ if entry['role'] == 'You':
698
+ text = f"👤 {entry['content']}"
699
+ if entry.get('is_correct'):
700
+ style = ParagraphStyle(
701
+ 'Correct',
702
+ parent=story_style,
703
+ textColor=colors.darkgreen
704
+ )
705
+ else:
706
+ style = story_style
707
+ else: # AI response
708
+ text = f"🤖 {entry['content']}"
709
+ style = ParagraphStyle(
710
+ 'AI',
711
+ parent=story_style,
712
+ textColor=colors.navy
713
+ )
714
+ elements.append(Paragraph(text, style))
715
+
716
+ # เพิ่ม achievements ที่ได้รับ
717
+ if hasattr(st.session_state, 'achievements') and st.session_state.achievements:
718
+ elements.append(Spacer(1, 20))
719
+ elements.append(Paragraph("Achievements Earned", styles['Heading2']))
720
+ for achievement in st.session_state.achievements:
721
+ elements.append(Paragraph(f"🏆 {achievement}", stats_style))
722
+
723
+ # สร้าง PDF
724
+ doc.build(elements)
725
+ pdf = buffer.getvalue()
726
+ buffer.close()
727
+ return pdf
728
+
729
+ # เพิ่มฟังก์ชันโหลดความก้าวหน้า
730
+ def load_progress(uploaded_file):
731
+ """โหลดความก้าวหน้าจากไฟล์ JSON"""
732
+ try:
733
+ data = json.loads(uploaded_file.getvalue())
734
+ st.session_state.level = data['level']
735
+ st.session_state.story = data['story']
736
+ st.session_state.achievements = data['achievements']
737
+ st.session_state.points = data['points']
738
+ st.session_state.stats = data['stats']
739
+ st.session_state.stats['vocabulary_used'] = set(data['stats']['vocabulary_used'])
740
+ st.success("โหลดความก้าวหน้าเรียบร้อย!")
741
+ st.rerun()
742
+ except Exception as e:
743
+ st.error(f"เกิดข้อผิดพลาดในการโหลดไฟล์: {str(e)}")
744
+
745
+ def reset_story():
746
+ """Reset the story and related state variables."""
747
+ st.session_state.story = []
748
+ st.session_state.feedback = None
749
+ st.session_state.unique_words = set()
750
+ st.session_state.total_words = 0
751
+ st.session_state.achievements = [] # เปลี่ยนจาก badges เป็น achievements
752
+ st.session_state.should_reset = False
753
+
754
+ # เพิ่มการ reset points
755
+ st.session_state.points = {
756
+ 'total': 0,
757
+ 'perfect_sentences': 0,
758
+ 'corrections_made': 0,
759
+ 'streak': 0,
760
+ 'max_streak': 0
761
+ }
762
+
763
+ # เพิ่มการ reset stats
764
+ st.session_state.stats = {
765
+ 'total_sentences': 0,
766
+ 'correct_first_try': 0,
767
+ 'accuracy_rate': 0.0,
768
+ 'vocabulary_used': set()
769
+ }
770
+
771
+ # Handle story reset if needed
772
+ if st.session_state.should_reset:
773
+ reset_story()
774
+
775
+ # Main UI Layout
776
+ st.markdown("# 📖 JoyStory")
777
+ show_welcome_section()
778
+ show_parent_guide() # Add parent guide right after welcome section
779
+
780
+ # Sidebar for settings
781
+ with st.sidebar:
782
+ st.markdown("### 📂 โหลดความก้าวหน้า")
783
+ uploaded_file = st.file_uploader(
784
+ "เลือกไฟล์ .json",
785
+ type=['json'],
786
+ help="เลือกไฟล์ความก้าวหน้าที่บันทึกไว้"
787
+ )
788
+ if uploaded_file:
789
+ if st.button("โหลดความก้าวหน้า"):
790
+ load_progress(uploaded_file)
791
+
792
+ st.markdown("""
793
+ <div class="thai">
794
+ 🎯 เลือกระดับการเรียนรู้
795
+ </div>
796
+ """, unsafe_allow_html=True)
797
+
798
+ level = st.radio(
799
+ "ระดับการเรียน", # เพิ่ม label
800
+ options=list(level_options.keys()),
801
+ format_func=lambda x: level_options[x]['thai_name'],
802
+ label_visibility="collapsed" # ซ่อน label แต่ยังคงมีสำหรับ accessibility
803
+ )
804
+
805
+ # แสดงคำอธิบายระดับ
806
+ st.markdown(f"""
807
+ <div class="level-info thai">
808
+ 🎓 {level_options[level]['description']}
809
+ <br>📚 เหมาะสำหรับอายุ: {level_options[level]['age_range']}
810
+ </div>
811
+ """, unsafe_allow_html=True)
812
+
813
+ st.session_state.level = level
814
+
815
+ if st.button("เริ่มเรื่องใหม่ | Start New Story"):
816
+ st.session_state.should_reset = True
817
+ st.rerun()
818
+
819
+
820
+ # Main content area
821
+ col1, col2 = st.columns([3, 1])
822
+
823
+ with col1:
824
+ # Story Display Box
825
+ st.markdown("""
826
+ <div class="thai-eng">
827
+ <div class="thai">📖 เรื่องราวของคุณ</div>
828
+ <div class="eng">Your Story</div>
829
+ </div>
830
+ """, unsafe_allow_html=True)
831
+
832
+ story_display = st.container()
833
+
834
+ with story_display:
835
+ if not st.session_state.story:
836
+ st.info("เริ่มต้นผจญภัยด้วยการเขียนประโยคแรกกันเลย! | Start your adventure by writing the first sentence!")
837
+ else:
838
+ for idx, entry in enumerate(st.session_state.story):
839
+ if entry['role'] == 'You':
840
+ # เพิ่มไอคอนแสดงสถานะความถูกต้อง
841
+ status_icon = "✅ " if entry.get('is_correct') else "✍️ "
842
+ st.write(f"👤 You: {status_icon}{entry['content']}")
843
+ elif entry['role'] == 'AI':
844
+ st.write("🤖 AI:", entry['content'])
845
+
846
+ # User Input Box
847
+ st.markdown("""
848
+ <div class="thai-eng">
849
+ <div class="thai">✏️ ถึงตาคุณแล้ว</div>
850
+ <div class="eng">Your Turn</div>
851
+ </div>
852
+ """, unsafe_allow_html=True)
853
+
854
+ # Text input with callback
855
+ st.text_area(
856
+ "เขียนต่อจากเรื่องราว | Continue the story:",
857
+ height=100,
858
+ key="text_input",
859
+ label_visibility="collapsed"
860
+ )
861
+
862
+ # Submit button with callback
863
+ st.button(
864
+ "ส่งคำตอบ | Submit",
865
+ on_click=submit_story
866
+ )
867
+
868
+ with col2:
869
+ if st.session_state.feedback:
870
+ st.markdown("""
871
+ <div class="thai-eng">
872
+ <div class="thai">📝 คำแนะนำจากครู</div>
873
+ <div class="eng">Writing Feedback</div>
874
+ </div>
875
+ """, unsafe_allow_html=True)
876
+
877
+ feedback_data = st.session_state.feedback
878
+ if isinstance(feedback_data, dict) and feedback_data.get('has_errors'):
879
+ st.markdown(f"""
880
+ <div style='background-color: #f0f2f6;
881
+ padding: 15px;
882
+ border-radius: 8px;
883
+ border-left: 4px solid #FF9800;
884
+ margin: 5px 0;
885
+ font-family: "Sarabun", sans-serif;'>
886
+ <p style='color: #1e88e5; margin-bottom: 10px;'>
887
+ {feedback_data['feedback']}
888
+ </p>
889
+ <p style='color: #666; font-size: 0.9em;'>
890
+ ประโยคที่ถูกต้อง:<br/>
891
+ <span style='color: #4CAF50; font-weight: bold;'>
892
+ {feedback_data['corrected']}
893
+ </span>
894
+ </p>
895
+ </div>
896
+ """, unsafe_allow_html=True)
897
+
898
+ # แสดงปุ่มแก้ไข
899
+ if st.button("✍️ แก้ไขประโยคให้ถูกต้อง", key="correct_button"):
900
+ last_user_entry_idx = next(
901
+ (i for i, entry in reversed(list(enumerate(st.session_state.story)))
902
+ if entry['role'] == 'You'),
903
+ None
904
+ )
905
+ if last_user_entry_idx is not None:
906
+ apply_correction(
907
+ last_user_entry_idx,
908
+ feedback_data['corrected']
909
+ )
910
+ st.rerun()
911
+ else:
912
+ st.markdown(f"""
913
+ <div style='background-color: #f0f2f6;
914
+ padding: 15px;
915
+ border-radius: 8px;
916
+ border-left: 4px solid #4CAF50;
917
+ margin: 5px 0;
918
+ font-family: "Sarabun", sans-serif;'>
919
+ <p style='color: #1e88e5;'>
920
+ {feedback_data.get('feedback', '✨ เขียนได้ถูกต้องแล้วค่ะ!')}
921
+ </p>
922
+ </div>
923
+ """, unsafe_allow_html=True)
924
+
925
+ # Help and Suggestions Box
926
+ st.markdown("""
927
+ <div class="thai-eng">
928
+ <div class="thai">✨ เครื่องมือช่วยเขียน</div>
929
+ <div class="eng">Writing Tools</div>
930
+ </div>
931
+ """, unsafe_allow_html=True)
932
+
933
+ if st.button("ดูคำศัพท์แนะนำ | Get Vocabulary Ideas"):
934
+ vocab_suggestions = get_vocabulary_suggestions()
935
+ st.markdown("#### 📚 คำศัพท์น่ารู้ | Useful Words")
936
+ for word in vocab_suggestions:
937
+ st.markdown(f"• {word}")
938
+
939
+ if st.button("ขอคำใบ้ | Get Creative Prompt"):
940
+ prompt = get_creative_prompt()
941
+ st.markdown(f"""
942
+ <div class="thai-eng">
943
+ <div class="thai">💭 {prompt['thai']}</div>
944
+ <div class="eng">💭 {prompt['eng']}</div>
945
+ </div>
946
+ """, unsafe_allow_html=True)
947
+
948
+ st.markdown("---") # เส้นคั่น
949
+ show_achievements()
950
+
951
+ # Achievements Box
952
+ st.markdown("""
953
+ <div class="thai-eng">
954
+ <div class="thai">🏆 ความสำเร็จ</div>
955
+ <div class="eng">Achievements</div>
956
+ </div>
957
+ """, unsafe_allow_html=True)
958
+
959
+ # Save Story Button
960
+ if st.session_state.story:
961
+ st.markdown("### 💾 บันทึกเรื่องราว")
962
+ col1, col2 = st.columns(2)
963
+
964
+ with col1:
965
+ # บันทึกเป็น PDF
966
+ pdf = create_story_pdf()
967
+ st.download_button(
968
+ label="📑 ดาวน์โหลดเป็น PDF",
969
+ data=pdf,
970
+ file_name=f"my_story_{datetime.now().strftime('%Y%m%d')}.pdf",
971
+ mime="application/pdf"
972
+ )
973
+
974
+ with col2:
975
+ # บันทึกข้อมูลดิบ (JSON) สำหรับโหลดกลับมาเล่นต่อ
976
+ story_data = {
977
+ 'level': st.session_state.level,
978
+ 'date': datetime.now().isoformat(),
979
+ 'story': st.session_state.story,
980
+ 'achievements': st.session_state.achievements,
981
+ 'points': st.session_state.points,
982
+ 'stats': {
983
+ 'total_sentences': st.session_state.stats['total_sentences'],
984
+ 'correct_first_try': st.session_state.stats['correct_first_try'],
985
+ 'accuracy_rate': st.session_state.stats['accuracy_rate'],
986
+ 'vocabulary_used': list(st.session_state.stats['vocabulary_used'])
987
+ }
988
+ }
989
+ st.download_button(
990
+ label="💾 บันทึกความก้าวหน้า",
991
+ data=json.dumps(story_data, indent=2),
992
+ file_name=f"story_progress_{datetime.now().strftime('%Y%m%d')}.json",
993
+ mime="application/json"
994
+ )