Rathapoom commited on
Commit
60dc465
·
verified ·
1 Parent(s): cef92c8

Rename app_claude.py to app_work.py

Browse files
Files changed (2) hide show
  1. app_claude.py +0 -725
  2. app_work.py +2212 -0
app_claude.py DELETED
@@ -1,725 +0,0 @@
1
- import streamlit as st
2
- import openai
3
- from datetime import datetime
4
- import json
5
- from collections import defaultdict
6
-
7
- # Custom CSS for layout
8
- st.markdown("""
9
- <style>
10
- .box-container {
11
- background-color: #ffffff;
12
- border-radius: 10px;
13
- padding: 20px;
14
- margin: 10px 0;
15
- border: 2px solid #eee;
16
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
17
- }
18
-
19
- .story-box {
20
- max-height: 300px;
21
- overflow-y: auto;
22
- padding: 15px;
23
- background-color: #f8f9fa;
24
- border-radius: 8px;
25
- }
26
-
27
- .input-box {
28
- background-color: #ffffff;
29
- padding: 15px;
30
- border-radius: 8px;
31
- }
32
-
33
- .help-box {
34
- background-color: #f0f7ff;
35
- padding: 15px;
36
- border-radius: 8px;
37
- }
38
-
39
- .rewards-box {
40
- background-color: #fff9f0;
41
- padding: 15px;
42
- border-radius: 8px;
43
- }
44
-
45
- .stButton button {
46
- width: 100%;
47
- border-radius: 20px;
48
- margin: 5px 0;
49
- }
50
-
51
- .story-text {
52
- padding: 10px;
53
- border-radius: 5px;
54
- margin: 5px 0;
55
- }
56
-
57
- .ai-text {
58
- background-color: #e3f2fd;
59
- }
60
-
61
- .player-text {
62
- background-color: #f0f4c3;
63
- }
64
-
65
- .prompt-text {
66
- color: #666;
67
- font-style: italic;
68
- margin: 5px 0;
69
- }
70
-
71
- .badge-container {
72
- display: inline-block;
73
- margin: 5px;
74
- padding: 10px;
75
- background-color: #ffd700;
76
- border-radius: 50%;
77
- text-align: center;
78
- }
79
- </style>
80
- """, unsafe_allow_html=True)
81
-
82
- # Initialize OpenAI
83
- openai.api_key = st.secrets["OPENAI_API_KEY"]
84
-
85
- # Initialize session state variables
86
- if 'story' not in st.session_state:
87
- st.session_state.story = []
88
- if 'current_level' not in st.session_state:
89
- st.session_state.current_level = 'beginner'
90
- if 'story_started' not in st.session_state:
91
- st.session_state.story_started = False
92
- if 'first_turn' not in st.session_state:
93
- st.session_state.first_turn = None
94
- if 'current_turn' not in st.session_state:
95
- st.session_state.current_turn = None
96
- if 'badges' not in st.session_state:
97
- st.session_state.badges = defaultdict(int)
98
- if 'vocabulary_used' not in st.session_state:
99
- st.session_state.vocabulary_used = set()
100
-
101
- def get_ai_story_continuation(story_context, level):
102
- """Get story continuation from OpenAI API"""
103
- try:
104
- response = openai.ChatCompletion.create(
105
- model="gpt-4o-mini",
106
- messages=[
107
- {"role": "system", "content": f"""You are a creative storyteller helping a {level} level English student write a story.
108
-
109
- Rules:
110
- 1. READ the story context carefully
111
- 2. ADD meaningful story elements (new events, character development, dialogue)
112
- 3. AVOID generic transitions like 'suddenly' or 'then'
113
- 4. ENSURE story progression and character development
114
- 5. USE vocabulary appropriate for {level} level
115
-
116
- Current story elements:
117
- - Characters introduced: {extract_characters(story_context)}
118
- - Current setting: {extract_setting(story_context)}
119
- - Recent events: {extract_recent_events(story_context)}
120
-
121
- Provide JSON response with:
122
- 1. continuation: Create an interesting next story event (1-2 sentences)
123
- 2. creative_prompt: Ask about specific details/choices for the next part
124
- 3. vocabulary_suggestions: 3 contextual words for the next part
125
- 4. translation: Thai translation of continuation
126
- 5. story_elements: New elements introduced (characters/places/objects)"""},
127
- {"role": "user", "content": f"Story context: {story_context}\nContinue the story creatively and maintain narrative flow."}
128
- ],
129
- temperature=0.7
130
- )
131
-
132
- result = json.loads(response.choices[0].message.content)
133
-
134
- # Validate story continuation
135
- if is_generic_response(result['continuation']):
136
- return get_ai_story_continuation(story_context, level)
137
-
138
- return result
139
-
140
- except Exception as e:
141
- st.error(f"Error with AI response: {e}")
142
- return create_fallback_response(story_context)
143
-
144
- def extract_characters(story_context):
145
- """Extract character information from story"""
146
- try:
147
- char_response = openai.ChatCompletion.create(
148
- model="gpt-4o-mini",
149
- messages=[
150
- {"role": "system", "content": "List all characters mentioned in the story."},
151
- {"role": "user", "content": story_context}
152
- ]
153
- )
154
- return char_response.choices[0].message.content
155
- except:
156
- return "Unknown characters"
157
-
158
- def extract_setting(story_context):
159
- """Extract setting information from story"""
160
- try:
161
- setting_response = openai.ChatCompletion.create(
162
- model="gpt-4o-mini",
163
- messages=[
164
- {"role": "system", "content": "Describe the current setting of the story."},
165
- {"role": "user", "content": story_context}
166
- ]
167
- )
168
- return setting_response.choices[0].message.content
169
- except:
170
- return "Unknown setting"
171
-
172
- def extract_recent_events(story_context):
173
- """Extract recent events from story"""
174
- try:
175
- events_response = openai.ChatCompletion.create(
176
- model="gpt-4o-mini",
177
- messages=[
178
- {"role": "system", "content": "Summarize the most recent events in the story."},
179
- {"role": "user", "content": story_context}
180
- ]
181
- )
182
- return events_response.choices[0].message.content
183
- except:
184
- return "Unknown events"
185
-
186
- def is_generic_response(continuation):
187
- """Check if the response is too generic"""
188
- generic_phrases = [
189
- "suddenly", "then", "something happened",
190
- "unexpectedly", "the story continues"
191
- ]
192
- return any(phrase in continuation.lower() for phrase in generic_phrases)
193
-
194
- def create_fallback_response(story_context):
195
- """Create contextual fallback response"""
196
- # Extract last event or setting from story_context
197
- last_event = story_context.split('.')[-2] if len(story_context.split('.')) > 1 else story_context
198
-
199
- return {
200
- "continuation": f"The characters decided to explore further into the story.",
201
- "creative_prompt": "What interesting detail should we add to the scene?",
202
- "vocabulary_suggestions": ["explore", "adventure", "discover"],
203
- "translation": "ตัวละครตัดสินใจสำรวจเรื่องราวต่อไป",
204
- "story_elements": {"new_elements": ["exploration"]}
205
- }
206
-
207
- def validate_story_quality(story_context, new_continuation):
208
- """Validate that the story maintains quality and coherence"""
209
- try:
210
- validation = openai.ChatCompletion.create(
211
- model="gpt-4o-mini",
212
- messages=[
213
- {"role": "system", "content": """Validate story coherence and quality.
214
- Return JSON with:
215
- 1. is_coherent: boolean
216
- 2. reason: string explanation if not coherent"""},
217
- {"role": "user", "content": f"""Previous: {story_context}
218
- New continuation: {new_continuation}
219
- Is this a logical and non-repetitive continuation?"""}
220
- ]
221
- )
222
-
223
- result = json.loads(validation.choices[0].message.content)
224
- return result['is_coherent'], result.get('reason', '')
225
-
226
- except Exception:
227
- return True, "" # Default to accepting if validation fails
228
-
229
- def start_story(level):
230
- """Get the first sentence from AI"""
231
- # Define level-specific starting prompts
232
- level_prompts = {
233
- 'beginner': {
234
- 'guidance': "Start with a simple, engaging sentence about a person, animal, or place.",
235
- 'examples': "Example: 'A happy dog lived in a blue house.'"
236
- },
237
- 'intermediate': {
238
- 'guidance': "Start with an interesting situation using compound sentences.",
239
- 'examples': "Example: 'On a sunny morning, Sarah found a mysterious letter in her garden.'"
240
- },
241
- 'advanced': {
242
- 'guidance': "Start with a sophisticated hook using complex sentence structure.",
243
- 'examples': "Example: 'Deep in the ancient forest, where shadows danced beneath ancient trees, a peculiar sound echoed through the mist.'"
244
- }
245
- }
246
-
247
- try:
248
- response = openai.ChatCompletion.create(
249
- model="gpt-4o-mini", # หรือ model ที่ถูกต้องตามที่คุณใช้
250
- messages=[
251
- {"role": "system", "content": f"""You are starting a story for a {level} level English student.
252
- {level_prompts[level]['guidance']}
253
- {level_prompts[level]['examples']}
254
-
255
- Provide a JSON response with:
256
- 1. opening: An engaging opening sentence (matching the specified level)
257
- 2. creative_prompt: A short question (3-8 words) to guide the student's first sentence
258
- 3. vocabulary_suggestions: 3 words they might use in their response (appropriate for {level} level)
259
- 4. translation: Thai translation of the opening sentence
260
-
261
- Make the opening engaging but appropriate for the student's level."""},
262
- {"role": "user", "content": "Please start a new story."}
263
- ],
264
- temperature=0.7
265
- )
266
- return json.loads(response.choices[0].message.content)
267
- except Exception as e:
268
- st.error(f"Error with AI response: {e}")
269
- return {
270
- "opening": "Once upon a time...",
271
- "creative_prompt": "Who is our main character?",
272
- "vocabulary_suggestions": ["brave", "curious", "friendly"],
273
- "translation": "กาลครั้งหนึ่ง..."
274
- }
275
-
276
- def main():
277
- st.title("🎨 Interactive Story Adventure")
278
-
279
- # Initial setup (level selection and first turn choice)
280
- if not st.session_state.story_started:
281
- with st.container():
282
- st.markdown('<div class="box-container">', unsafe_allow_html=True)
283
- st.write("### Welcome to Story Adventure!")
284
- col1, col2 = st.columns(2)
285
- with col1:
286
- level = st.selectbox(
287
- "Choose your English level:",
288
- ['beginner', 'intermediate', 'advanced']
289
- )
290
- with col2:
291
- first_turn = st.radio(
292
- "Who should start the story?",
293
- ['AI', 'Player']
294
- )
295
- if st.button("Start Story!", key="start_button"):
296
- st.session_state.current_level = level
297
- st.session_state.first_turn = first_turn
298
- st.session_state.current_turn = first_turn
299
- st.session_state.story_started = True
300
- if first_turn == 'AI':
301
- response = start_story(level)
302
- st.session_state.story.append({
303
- 'author': 'AI',
304
- 'text': response['opening'],
305
- 'prompt': response['creative_prompt'],
306
- 'vocabulary': response['vocabulary_suggestions'],
307
- 'timestamp': datetime.now().isoformat()
308
- })
309
- st.session_state.current_turn = 'Player'
310
- st.rerun()
311
- st.markdown('</div>', unsafe_allow_html=True)
312
-
313
- # Main story interface with 4-box layout
314
- if st.session_state.story_started:
315
- # Box 1: Story Display
316
- with st.container():
317
- st.markdown('<div class="box-container story-box">', unsafe_allow_html=True)
318
- st.write("### 📖 Our Story So Far:")
319
-
320
- for entry in st.session_state.story:
321
- # แสดงเนื้อเรื่อง
322
- css_class = "ai-text" if entry['author'] == 'AI' else "player-text"
323
- st.markdown(f'<div class="story-text {css_class}">{entry["text"]}</div>',
324
- unsafe_allow_html=True)
325
-
326
- # ถ้าเป็น AI response
327
- if entry['author'] == 'AI':
328
- # แสดงคำแปล
329
- if 'translation' in entry:
330
- st.markdown(f'''
331
- <div class="translation-box">
332
- 🇹🇭 {entry["translation"]}
333
- </div>
334
- ''', unsafe_allow_html=True)
335
-
336
- # แสดงคำถามกระตุ้นความคิด
337
- if 'prompt' in entry:
338
- st.markdown(f'''
339
- <div class="prompt-box">
340
- 💭 {entry["prompt"]}
341
- </div>
342
- ''', unsafe_allow_html=True)
343
-
344
- # แสดงคำศัพท์แนะนำ
345
- if 'vocabulary' in entry:
346
- st.markdown(f'''
347
- <div class="vocabulary-box">
348
- 📚 Try using: {', '.join(entry["vocabulary"])}
349
- </div>
350
- ''', unsafe_allow_html=True)
351
-
352
- # แสดงองค์ประกอบใหม่ในเรื่อง
353
- if 'story_elements' in entry:
354
- st.markdown(f'''
355
- <div class="elements-box">
356
- 🎭 New elements: {', '.join(entry["story_elements"]["new_elements"])}
357
- </div>
358
- ''', unsafe_allow_html=True)
359
-
360
- # Box 2: Writing Area
361
- if st.session_state.current_turn == 'Player':
362
- with st.container():
363
- st.markdown('<div class="box-container input-box">', unsafe_allow_html=True)
364
- st.write("### ✏️ Your Turn!")
365
- user_input = st.text_area("Write your next sentence:", key='story_input')
366
- if st.button("✨ Submit", key="submit_button"):
367
- if user_input:
368
- process_player_input(user_input)
369
- st.rerun()
370
- st.markdown('</div>', unsafe_allow_html=True)
371
- else:
372
- st.info("🤖 AI is thinking...") # แสดงสถานะเมื่อเป็น turn ของ AI
373
-
374
- # Box 3: Help Buttons
375
- with st.container():
376
- st.markdown('<div class="box-container help-box">', unsafe_allow_html=True)
377
- st.write("### 💡 Need Help?")
378
- col1, col2, col3 = st.columns(3)
379
- with col1:
380
- if st.button("📚 Vocabulary Help"):
381
- show_vocabulary_help()
382
- with col2:
383
- if st.button("❓ Writing Tips"):
384
- show_writing_tips()
385
- with col3:
386
- if st.button("🎯 Story Ideas"):
387
- show_story_ideas()
388
- st.markdown('</div>', unsafe_allow_html=True)
389
-
390
- # Box 4: Rewards and Progress
391
- with st.container():
392
- st.markdown('<div class="box-container rewards-box">', unsafe_allow_html=True)
393
- st.write("### 🏆 Your Achievements")
394
- show_achievements()
395
- col1, col2 = st.columns(2)
396
- with col1:
397
- if st.button("📥 Save Story"):
398
- save_story()
399
- with col2:
400
- if st.button("🔄 Start New Story"):
401
- reset_story()
402
- st.rerun()
403
- st.markdown('</div>', unsafe_allow_html=True)
404
-
405
- def process_player_input(user_input):
406
- try:
407
- # Get story context for AI
408
- story_context = " ".join([entry['text'] for entry in st.session_state.story])
409
-
410
- # Add player's sentence first
411
- st.session_state.story.append({
412
- 'author': 'Player',
413
- 'text': user_input,
414
- 'timestamp': datetime.now().isoformat()
415
- })
416
-
417
- # Get AI's continuation immediately after player's input
418
- ai_response = get_ai_story_continuation(
419
- story_context + "\n" + user_input,
420
- st.session_state.current_level
421
- )
422
-
423
- # Validate the continuation
424
- is_coherent, reason = validate_story_quality(story_context, ai_response['continuation'])
425
-
426
- if not is_coherent:
427
- # Try getting a new response
428
- ai_response = get_ai_story_continuation(story_context + "\n" + user_input, st.session_state.current_level)
429
-
430
- # Add AI's response to story
431
- st.session_state.story.append({
432
- 'author': 'AI',
433
- 'text': ai_response['continuation'],
434
- 'prompt': ai_response['creative_prompt'],
435
- 'vocabulary': ai_response['vocabulary_suggestions'],
436
- 'translation': ai_response.get('translation', ''),
437
- 'timestamp': datetime.now().isoformat()
438
- })
439
-
440
- # Get feedback for player's writing
441
- feedback_response = openai.ChatCompletion.create(
442
- model="gpt-4o-mini",
443
- messages=[
444
- {"role": "system", "content": f"""You are an English teacher helping a {st.session_state.current_level} level student.
445
- Analyze their sentence and provide feedback in JSON format with:
446
- 1. grammar_check: List of grammar issues found (if any)
447
- 2. spelling_check: List of spelling issues found (if any)
448
- 3. improvement_suggestions: Specific suggestions for improvement
449
- 4. positive_feedback: What they did well
450
- Be encouraging but thorough in your feedback."""},
451
- {"role": "user", "content": f"Student's sentence: {user_input}"}
452
- ]
453
- )
454
- feedback = json.loads(feedback_response.choices[0].message.content)
455
-
456
- # Update vocabulary used
457
- words = set(user_input.lower().split())
458
- st.session_state.vocabulary_used.update(words)
459
-
460
- # Check for achievements
461
- check_achievements(user_input, feedback)
462
-
463
- # Show feedback to player
464
- display_feedback(feedback)
465
-
466
- except Exception as e:
467
- st.error(f"Error processing input: {e}")
468
- st.session_state.story.append({
469
- 'author': 'Player',
470
- 'text': user_input,
471
- 'timestamp': datetime.now().isoformat()
472
- })
473
-
474
- def check_achievements(user_input, feedback):
475
- """Check and award achievements based on player's writing"""
476
- # Story length achievements
477
- if len(st.session_state.story) >= 10:
478
- award_badge("Storyteller")
479
- if len(st.session_state.story) >= 20:
480
- award_badge("Master Storyteller")
481
-
482
- # Vocabulary achievements
483
- if len(st.session_state.vocabulary_used) >= 20:
484
- award_badge("Vocabulary Explorer")
485
- if len(st.session_state.vocabulary_used) >= 50:
486
- award_badge("Word Master")
487
-
488
- # Writing quality achievements
489
- if not feedback['grammar_check'] and not feedback['spelling_check']:
490
- award_badge("Perfect Writing")
491
-
492
- # Creativity achievements
493
- if len(user_input.split()) >= 15:
494
- award_badge("Detailed Writer")
495
-
496
- def award_badge(badge_name):
497
- """Award a new badge if not already earned"""
498
- if badge_name not in st.session_state.badges:
499
- st.session_state.badges[badge_name] = 1
500
- st.balloons()
501
- st.success(f"🏆 New Achievement Unlocked: {badge_name}!")
502
- else:
503
- st.session_state.badges[badge_name] += 1
504
-
505
- def display_feedback(feedback):
506
- """Display writing feedback to the player"""
507
- with st.container():
508
- # Show positive feedback first
509
- st.success(f"👏 {feedback['positive_feedback']}")
510
-
511
- # Show any grammar or spelling issues
512
- if feedback['grammar_check']:
513
- st.warning("📝 Grammar points to consider:")
514
- for issue in feedback['grammar_check']:
515
- st.write(f"- {issue}")
516
-
517
- if feedback['spelling_check']:
518
- st.warning("✍️ Spelling points to consider:")
519
- for issue in feedback['spelling_check']:
520
- st.write(f"- {issue}")
521
-
522
- # Show improvement suggestions
523
- if feedback['improvement_suggestions']:
524
- st.info("💡 Suggestions for next time:")
525
- for suggestion in feedback['improvement_suggestions']:
526
- st.write(f"- {suggestion}")
527
-
528
- def show_vocabulary_help():
529
- """Show vocabulary suggestions and meanings"""
530
- if st.session_state.story:
531
- last_entry = st.session_state.story[-1]
532
- if 'vocabulary' in last_entry:
533
- # Get word meanings and examples
534
- try:
535
- word_info = openai.ChatCompletion.create(
536
- model="gpt-4o-mini",
537
- messages=[
538
- {"role": "system", "content": f"""For each word, provide:
539
- 1. Simple definition
540
- 2. Thai translation
541
- 3. Example sentence
542
- Format in JSON with word as key."""},
543
- {"role": "user", "content": f"Words: {', '.join(last_entry['vocabulary'])}"}
544
- ]
545
- )
546
- word_details = json.loads(word_info.choices[0].message.content)
547
-
548
- st.write("### 📚 Suggested Words:")
549
- for word in last_entry['vocabulary']:
550
- with st.expander(f"🔤 {word}"):
551
- if word in word_details:
552
- details = word_details[word]
553
- st.write(f"**Meaning:** {details['definition']}")
554
- st.write(f"**แปลไทย:** {details['thai']}")
555
- st.write(f"**Example:** _{details['example']}_")
556
- except Exception as e:
557
- # Fallback to simple display
558
- st.write("Try using these words:")
559
- for word in last_entry['vocabulary']:
560
- st.markdown(f"- *{word}*")
561
-
562
- def show_writing_tips():
563
- """Show writing tips based on current level and story context"""
564
- if st.session_state.story:
565
- try:
566
- # Get personalized writing tips
567
- tips_response = openai.ChatCompletion.create(
568
- model="gpt-4o-mini",
569
- messages=[
570
- {"role": "system", "content": f"""You are helping a {st.session_state.current_level} level English student.
571
- Provide 3 specific writing tips based on their level and story context.
572
- Include examples for each tip.
573
- Keep tips short and friendly."""},
574
- {"role": "user", "content": f"Story so far: {' '.join([entry['text'] for entry in st.session_state.story])}"}
575
- ]
576
- )
577
-
578
- st.write("### ✍️ Writing Tips")
579
- tips = tips_response.choices[0].message.content.split('\n')
580
- for tip in tips:
581
- if tip.strip():
582
- st.info(tip)
583
-
584
- # Add quick reference examples
585
- st.write("### 🎯 Quick Examples:")
586
- col1, col2 = st.columns(2)
587
- with col1:
588
- st.write("**Good:**")
589
- st.success("The happy dog played in the garden.")
590
- with col2:
591
- st.write("**Better:**")
592
- st.success("The excited golden retriever chased butterflies in the colorful garden.")
593
-
594
- except Exception as e:
595
- # Fallback tips
596
- st.write("### General Writing Tips:")
597
- st.write("1. Use descriptive words")
598
- st.write("2. Keep your sentences clear")
599
- st.write("3. Try to be creative")
600
-
601
- def show_story_ideas():
602
- """Generate creative story ideas based on current context"""
603
- if st.session_state.story:
604
- try:
605
- # Get creative prompts
606
- ideas_response = openai.ChatCompletion.create(
607
- model="gpt-4o-mini",
608
- messages=[
609
- {"role": "system", "content": f"""Generate 3 creative story direction ideas for a {st.session_state.current_level} student.
610
- Ideas should follow from the current story.
611
- Make suggestions exciting but achievable for their level."""},
612
- {"role": "user", "content": f"Story so far: {' '.join([entry['text'] for entry in st.session_state.story])}"}
613
- ]
614
- )
615
-
616
- st.write("### 💭 Story Ideas")
617
- ideas = ideas_response.choices[0].message.content.split('\n')
618
- for i, idea in enumerate(ideas, 1):
619
- if idea.strip():
620
- with st.expander(f"Idea {i}"):
621
- st.write(idea)
622
-
623
- # Add story element suggestions
624
- st.write("### 🎨 Try adding:")
625
- elements = ["a new character", "a surprise event", "a funny moment", "a problem to solve"]
626
- cols = st.columns(len(elements))
627
- for col, element in zip(cols, elements):
628
- with col:
629
- st.button(element, key=f"element_{element}")
630
-
631
- except Exception as e:
632
- # Fallback ideas
633
- st.write("### Story Ideas:")
634
- st.write("1. Introduce a new character")
635
- st.write("2. Add an unexpected event")
636
- st.write("3. Create a problem to solve")
637
-
638
- def show_achievements():
639
- """Display achievements and badges with animations"""
640
- if st.session_state.badges:
641
- st.write("### 🏆 Your Achievements")
642
- cols = st.columns(3)
643
- for i, (badge, count) in enumerate(st.session_state.badges.items()):
644
- with cols[i % 3]:
645
- st.markdown(f'''
646
- <div class="badge-container" style="animation: bounce 1s infinite;">
647
- <div style="font-size: 2em;">🌟</div>
648
- <div style="font-weight: bold;">{badge}</div>
649
- <div>x{count}</div>
650
- </div>
651
- ''', unsafe_allow_html=True)
652
-
653
- # Add progress towards next badge
654
- st.write("### 📈 Progress to Next Badge:")
655
- current_words = len(st.session_state.vocabulary_used)
656
- next_milestone = (current_words // 10 + 1) * 10
657
- progress = current_words / next_milestone
658
- st.progress(progress)
659
- st.write(f"Use {next_milestone - current_words} more unique words for next badge!")
660
-
661
- def save_story():
662
- """Save story with additional information"""
663
- if st.session_state.story:
664
- # Prepare story text with formatting
665
- story_parts = []
666
- story_parts.append("=== My English Story Adventure ===\n")
667
- story_parts.append(f"Level: {st.session_state.current_level}")
668
- story_parts.append(f"Date: {datetime.now().strftime('%Y-%m-%d')}\n")
669
-
670
- # Add story content
671
- for entry in st.session_state.story:
672
- author = "👤 You:" if entry['author'] == 'Player' else "🤖 AI:"
673
- story_parts.append(f"{author} {entry['text']}")
674
-
675
- # Add achievements
676
- story_parts.append("\n=== Achievements ===")
677
- for badge, count in st.session_state.badges.items():
678
- story_parts.append(f"🌟 {badge}: x{count}")
679
-
680
- # Create the full story text
681
- story_text = "\n".join(story_parts)
682
-
683
- # Offer download options
684
- col1, col2 = st.columns(2)
685
- with col1:
686
- st.download_button(
687
- label="📝 Download Story (Text)",
688
- data=story_text,
689
- file_name="my_story.txt",
690
- mime="text/plain"
691
- )
692
- with col2:
693
- # Create JSON version with more details
694
- story_data = {
695
- "metadata": {
696
- "level": st.session_state.current_level,
697
- "date": datetime.now().isoformat(),
698
- "vocabulary_used": list(st.session_state.vocabulary_used),
699
- "achievements": dict(st.session_state.badges)
700
- },
701
- "story": st.session_state.story
702
- }
703
- st.download_button(
704
- label="💾 Download Progress Report (JSON)",
705
- data=json.dumps(story_data, indent=2),
706
- file_name="story_progress.json",
707
- mime="application/json"
708
- )
709
-
710
- def reset_story():
711
- """Reset all story-related session state"""
712
- if st.button("🔄 Start New Story", key="reset_button"):
713
- st.session_state.story = []
714
- st.session_state.story_started = False
715
- st.session_state.first_turn = None
716
- st.session_state.current_turn = None
717
- st.session_state.vocabulary_used = set()
718
- # Keep badges for long-term progress
719
- st.balloons()
720
- st.success("Ready for a new adventure! Your achievements have been saved.")
721
- st.rerun()
722
-
723
-
724
- if __name__ == "__main__":
725
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_work.py ADDED
@@ -0,0 +1,2212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # === 1. IMPORTS & SETUP ===
2
+ import streamlit as st
3
+ import json
4
+ import datetime
5
+ import logging
6
+ from openai import OpenAI
7
+ from typing import Dict, List, Set, Tuple
8
+ import io
9
+ from reportlab.lib import colors
10
+ from reportlab.lib.pagesizes import A4
11
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
12
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
13
+ from datetime import datetime
14
+ import random
15
+
16
+ # === 2. CONFIGURATIONS ===
17
+ # Theme Configuration
18
+ story_themes = {
19
+ 'fantasy': {
20
+ 'id': 'fantasy',
21
+ 'icon': '🏰',
22
+ 'name_en': 'Fantasy & Magic',
23
+ 'name_th': 'แฟนตาซีและเวทมนตร์',
24
+ 'description_th': 'ผจญภัยในโลกแห่งเวทมนตร์และจินตนาการ',
25
+ 'description_en': 'Adventure in a world of magic and imagination',
26
+ 'level_range': ['Beginner', 'Intermediate', 'Advanced'],
27
+ 'vocabulary': {
28
+ 'Beginner': ['dragon', 'magic', 'wand', 'spell', 'wizard', 'fairy', 'castle', 'king', 'queen'],
29
+ 'Intermediate': ['potion', 'enchanted', 'castle', 'creature', 'power', 'scroll', 'portal', 'magical'],
30
+ 'Advanced': ['sorcery', 'mystical', 'enchantment', 'prophecy', 'ancient', 'legendary', 'mythical']
31
+ },
32
+ 'story_starters': {
33
+ 'Beginner': [
34
+ {'th': 'วันหนึ่งฉันเจอไม้วิเศษในสวน...', 'en': 'One day, I found a magic wand in the garden...'},
35
+ {'th': 'มังกรน้อยกำลังมองหาเพื่อน...', 'en': 'The little dragon was looking for a friend...'},
36
+ {'th': 'เจ้าหญิงน้อยมีความลับวิเศษ...', 'en': 'The little princess had a magical secret...'}
37
+ ],
38
+ 'Intermediate': [
39
+ {'th': 'ในปราสาทเก่าแก่มีประตูลึกลับ...', 'en': 'In the ancient castle, there was a mysterious door...'},
40
+ {'th': 'เมื่อน้ำยาวิเศษเริ่มส่องแสง...', 'en': 'When the magic potion started to glow...'},
41
+ {'th': 'หนังสือเวทมนตร์เล่มนั้นเปิดออกเอง...', 'en': 'The spellbook opened by itself...'}
42
+ ],
43
+ 'Advanced': [
44
+ {'th': 'คำทำนายโบราณกล่าวถึงผู้วิเศษที่จะมา...', 'en': 'The ancient prophecy spoke of a wizard who would come...'},
45
+ {'th': 'ในโลกที่เวทมนตร์กำลังจะสูญหาย...', 'en': 'In a world where magic was fading away...'},
46
+ {'th': 'ณ จุดบรรจบของดวงดาวทั้งห้า...', 'en': 'At the convergence of the five stars...'}
47
+ ]
48
+ },
49
+ 'background_color': '#E8F3FF',
50
+ 'accent_color': '#1E88E5'
51
+ },
52
+ 'nature': {
53
+ 'id': 'nature',
54
+ 'icon': '🌳',
55
+ 'name_en': 'Nature & Animals',
56
+ 'name_th': 'ธรรมชาติและสัตว์โลก',
57
+ 'description_th': 'เรื่องราวของสัตว์น้อยใหญ่และธรรมชาติอันงดงาม',
58
+ 'description_en': 'Stories of animals and beautiful nature',
59
+ 'level_range': ['Beginner', 'Intermediate', 'Advanced'],
60
+ 'vocabulary': {
61
+ 'Beginner': ['tree', 'bird', 'flower', 'cat', 'dog', 'garden', 'rabbit', 'butterfly', 'sun'],
62
+ 'Intermediate': ['forest', 'river', 'mountain', 'wildlife', 'season', 'weather', 'rainbow', 'stream'],
63
+ 'Advanced': ['ecosystem', 'habitat', 'wilderness', 'environment', 'conservation', 'migration', 'climate']
64
+ },
65
+ 'story_starters': {
66
+ 'Beginner': [
67
+ {'th': 'แมวน้อยเจอนกในสวน...', 'en': 'The little cat found a bird in the garden...'},
68
+ {'th': 'ดอกไม้สวยกำลังเบ่งบาน...', 'en': 'The beautiful flower was blooming...'},
69
+ {'th': 'กระต่ายน้อยหลงทางในสวน...', 'en': 'The little rabbit got lost in the garden...'}
70
+ ],
71
+ 'Intermediate': [
72
+ {'th': 'ในป่าใหญ่มีเสียงลึกลับ...', 'en': 'In the big forest, there was a mysterious sound...'},
73
+ {'th': 'แม่น้ำสายนี้มีความลับ...', 'en': 'This river had a secret...'},
74
+ {'th': 'สายรุ้งพาดผ่านภูเขา...', 'en': 'A rainbow stretched across the mountain...'}
75
+ ],
76
+ 'Advanced': [
77
+ {'th': 'ฝูงนกกำลังอพยพย้ายถิ่น...', 'en': 'The birds were migrating...'},
78
+ {'th': 'ป่าฝนกำลังเปลี่ยนแปลง...', 'en': 'The rainforest was changing...'},
79
+ {'th': 'ความลับของระบบนิเวศ...', 'en': 'The secret of the ecosystem...'}
80
+ ]
81
+ },
82
+ 'background_color': '#F1F8E9',
83
+ 'accent_color': '#4CAF50'
84
+ },
85
+ 'space': {
86
+ 'id': 'space',
87
+ 'icon': '🚀',
88
+ 'name_en': 'Space Adventure',
89
+ 'name_th': 'ผจญภัยในอวกาศ',
90
+ 'description_th': 'เรื่องราวการสำรวจอวกาศและดวงดาวอันน่าตื่นเต้น',
91
+ 'description_en': 'Exciting stories of space exploration and celestial discoveries',
92
+ 'level_range': ['Beginner', 'Intermediate', 'Advanced'],
93
+ 'vocabulary': {
94
+ 'Beginner': ['star', 'moon', 'planet', 'sun', 'rocket', 'alien', 'space', 'light'],
95
+ 'Intermediate': ['astronaut', 'spacecraft', 'galaxy', 'meteor', 'satellite', 'orbit', 'comet'],
96
+ 'Advanced': ['constellation', 'nebula', 'astronomy', 'telescope', 'exploration', 'discovery']
97
+ },
98
+ 'story_starters': {
99
+ 'Beginner': [
100
+ {'th': 'จรวดลำน้อยพร้อมบินแล้ว...', 'en': 'The little rocket was ready to fly...'},
101
+ {'th': 'ดาวดวงน้อยเปล่งแสงวิบวับ...', 'en': 'The little star twinkled brightly...'},
102
+ {'th': 'มนุษย์ต่างดาวที่เป็นมิตร...', 'en': 'The friendly alien...'}
103
+ ],
104
+ 'Intermediate': [
105
+ {'th': 'นักบินอวกาศพบสิ่งประหลาด...', 'en': 'The astronaut found something strange...'},
106
+ {'th': 'ดาวเคราะห์ดวงใหม่ถูกค้นพบ...', 'en': 'A new planet was discovered...'},
107
+ {'th': 'สถานีอวกาศส่งสัญญาณลึกลับ...', 'en': 'The space station sent a mysterious signal...'}
108
+ ],
109
+ 'Advanced': [
110
+ {'th': 'การสำรวจดาวหางนำไปสู่การค้นพบ...', 'en': 'The comet exploration led to a discovery...'},
111
+ {'th': 'กาแล็กซี่ที่ไม่มีใครเคยเห็น...', 'en': 'An unknown galaxy appeared...'},
112
+ {'th': 'ความลับของหลุมดำ...', 'en': 'The secret of the black hole...'}
113
+ ]
114
+ },
115
+ 'background_color': '#E1F5FE',
116
+ 'accent_color': '#0288D1'
117
+ },
118
+ 'adventure': {
119
+ 'id': 'adventure',
120
+ 'icon': '🗺️',
121
+ 'name_en': 'Adventure & Quest',
122
+ 'name_th': 'การผจญภัยและการค้นหา',
123
+ 'description_th': 'ออกผจญภัยค้นหาสมบัติและความลับต่างๆ',
124
+ 'description_en': 'Embark on quests to find treasures and secrets',
125
+ 'level_range': ['Beginner', 'Intermediate', 'Advanced'],
126
+ 'vocabulary': {
127
+ 'Beginner': ['map', 'treasure', 'cave', 'island', 'path', 'boat', 'key', 'chest'],
128
+ 'Intermediate': ['compass', 'adventure', 'journey', 'mystery', 'explore', 'discover', 'quest'],
129
+ 'Advanced': ['expedition', 'archaeology', 'artifact', 'ancient', 'mysterious', 'discovery']
130
+ },
131
+ 'story_starters': {
132
+ 'Beginner': [
133
+ {'th': 'แผนที่เก่าแก่ชิ้นหนึ่ง...', 'en': 'An old map showed...'},
134
+ {'th': 'บนเกาะเล็กๆ มีสมบัติ...', 'en': 'On a small island, there was a treasure...'},
135
+ {'th': 'ถ้ำลึกลับถูกค้นพบ...', 'en': 'A mysterious cave was found...'}
136
+ ],
137
+ 'Intermediate': [
138
+ {'th': 'เข็มทิศวิเศษชี้ไปที่...', 'en': 'The magical compass pointed to...'},
139
+ {'th': 'การเดินทางเริ่มต้นที่...', 'en': 'The journey began at...'},
140
+ {'th': 'ความลับของวัตถุโบราณ...', 'en': 'The secret of the ancient artifact...'}
141
+ ],
142
+ 'Advanced': [
143
+ {'th': 'การสำรวจซากปรักหักพังนำไปสู่...', 'en': 'The ruins exploration led to...'},
144
+ {'th': 'นักโบราณคดีค้นพบ...', 'en': 'The archaeologist discovered...'},
145
+ {'th': 'ความลับของอารยธรรมโบราณ...', 'en': 'The secret of the ancient civilization...'}
146
+ ]
147
+ },
148
+ 'background_color': '#FFF3E0',
149
+ 'accent_color': '#FF9800'
150
+ },
151
+ 'school': {
152
+ 'id': 'school',
153
+ 'icon': '🏫',
154
+ 'name_en': 'School & Friends',
155
+ 'name_th': 'โรงเรียนและเพื่อน',
156
+ 'description_th': 'เรื่องราวสนุกๆ ในโรงเรียนกับเพื่อนๆ',
157
+ 'description_en': 'Fun stories about school life and friendship',
158
+ 'level_range': ['Beginner', 'Intermediate', 'Advanced'],
159
+ 'vocabulary': {
160
+ 'Beginner': ['friend', 'teacher', 'book', 'classroom', 'pencil', 'desk', 'lunch', 'play'],
161
+ 'Intermediate': ['homework', 'project', 'library', 'playground', 'student', 'lesson', 'study'],
162
+ 'Advanced': ['presentation', 'experiment', 'knowledge', 'research', 'collaboration', 'achievement']
163
+ },
164
+ 'story_starters': {
165
+ 'Beginner': [
166
+ {'th': 'วันแรกในห้องเรียนใหม่...', 'en': 'First day in the new classroom...'},
167
+ {'th': 'เพื่อนใหม่ในโรงเรียน...', 'en': 'A new friend at school...'},
168
+ {'th': 'ที่โต๊ะอาหารกลางวัน...', 'en': 'At the lunch table...'}
169
+ ],
170
+ 'Intermediate': [
171
+ {'th': 'โครงงานพิเศษของห้องเรา...', 'en': 'Our class special project...'},
172
+ {'th': 'ในห้องสมุดมีความลับ...', 'en': 'The library had a secret...'},
173
+ {'th': 'การทดลองวิทยาศาสตร์ครั้งนี้...', 'en': 'This science experiment...'}
174
+ ],
175
+ 'Advanced': [
176
+ {'th': 'การนำเสนอครั้งสำคัญ...', 'en': 'The important presentation...'},
177
+ {'th': 'การค้นคว้าพิเศษนำไปสู่...', 'en': 'The special research led to...'},
178
+ {'th': 'โครงการความร่วมมือระหว่างห้อง...', 'en': 'The inter-class collaboration project...'}
179
+ ]
180
+ },
181
+ 'background_color': '#F3E5F5',
182
+ 'accent_color': '#9C27B0'
183
+ },
184
+ 'superhero': {
185
+ 'id': 'superhero',
186
+ 'icon': '🦸',
187
+ 'name_en': 'Superheroes',
188
+ 'name_th': 'ซูเปอร์ฮีโร่',
189
+ 'description_th': 'เรื่องราวของฮีโร่ตัวน้อยผู้ช่วยเหลือผู้อื่น',
190
+ 'description_en': 'Stories of young heroes helping others',
191
+ 'level_range': ['Beginner', 'Intermediate', 'Advanced'],
192
+ 'vocabulary': {
193
+ 'Beginner': ['hero', 'help', 'save', 'power', 'mask', 'cape', 'fly', 'strong'],
194
+ 'Intermediate': ['rescue', 'protect', 'brave', 'courage', 'mission', 'team', 'secret', 'mighty'],
195
+ 'Advanced': ['superhero', 'extraordinary', 'responsibility', 'leadership', 'determination', 'justice']
196
+ },
197
+ 'story_starters': {
198
+ 'Beginner': [
199
+ {'th': 'ฮีโร่น้อยคนใหม่ของเมือง...', 'en': 'The city\'s new young hero...'},
200
+ {'th': 'พลังพิเศษของฉันทำให้...', 'en': 'My special power made me...'},
201
+ {'th': 'เมื่อต้องช่วยเหลือแมวตัวน้อย...', 'en': 'When I had to save a little cat...'}
202
+ ],
203
+ 'Intermediate': [
204
+ {'th': 'ภารกิจลับของทีมฮีโร่...', 'en': 'The hero team\'s secret mission...'},
205
+ {'th': 'พลังใหม่ที่น่าประหลาดใจ...', 'en': 'A surprising new power...'},
206
+ {'th': 'การช่วยเหลือครั้งสำคัญ...', 'en': 'An important rescue mission...'}
207
+ ],
208
+ 'Advanced': [
209
+ {'th': 'ความรับผิดชอบของการเป็นฮีโร่...', 'en': 'The responsibility of being a hero...'},
210
+ {'th': 'เมื่อเมืองต้องการฮีโร่...', 'en': 'When the city needed a hero...'},
211
+ {'th': 'การต่อสู้เพื่อความยุติธรรม...', 'en': 'Fighting for justice...'}
212
+ ]
213
+ },
214
+ 'background_color': '#FFE0B2',
215
+ 'accent_color': '#F57C00'
216
+ },
217
+ 'mystery': {
218
+ 'id': 'mystery',
219
+ 'icon': '🔍',
220
+ 'name_en': 'Mystery & Detective',
221
+ 'name_th': 'ไขปริศนาและนักสืบ',
222
+ 'description_th': 'สืบสวนปริศนาและไขความลับต่างๆ',
223
+ 'description_en': 'Solve mysteries and uncover secrets',
224
+ 'level_range': ['Beginner', 'Intermediate', 'Advanced'],
225
+ 'vocabulary': {
226
+ 'Beginner': ['clue', 'find', 'look', 'search', 'mystery', 'hidden', 'secret', 'detective'],
227
+ 'Intermediate': ['investigate', 'evidence', 'puzzle', 'solve', 'discover', 'suspicious', 'case'],
228
+ 'Advanced': ['investigation', 'deduction', 'enigma', 'cryptic', 'mysterious', 'revelation']
229
+ },
230
+ 'story_starters': {
231
+ 'Beginner': [
232
+ {'th': 'มีรอยปริศนาในสวน...', 'en': 'There were mysterious footprints in the garden...'},
233
+ {'th': 'จดหมายลึกลับถูกส่งมา...', 'en': 'A mysterious letter arrived...'},
234
+ {'th': 'ของเล่นหายไปอย่างลึกลับ...', 'en': 'The toy mysteriously disappeared...'}
235
+ ],
236
+ 'Intermediate': [
237
+ {'th': 'เบาะแสชิ้นแรกนำไปสู่...', 'en': 'The first clue led to...'},
238
+ {'th': 'ความลับในห้องเก่า...', 'en': 'The secret in the old room...'},
239
+ {'th': 'รหัสลับถูกค้นพบ...', 'en': 'A secret code was found...'}
240
+ ],
241
+ 'Advanced': [
242
+ {'th': 'คดีปริศนาที่ยากที่สุด...', 'en': 'The most challenging mystery case...'},
243
+ {'th': 'ความลับที่ซ่อนอยู่มานาน...', 'en': 'A long-hidden secret...'},
244
+ {'th': 'การสืบสวนนำไปสู่การค้นพบ...', 'en': 'The investigation led to a discovery...'}
245
+ ]
246
+ },
247
+ 'background_color': '#E0E0E0',
248
+ 'accent_color': '#616161'
249
+ },
250
+ 'science': {
251
+ 'id': 'science',
252
+ 'icon': '🔬',
253
+ 'name_en': 'Science & Discovery',
254
+ 'name_th': 'วิทยาศาสตร์และการค้นพบ',
255
+ 'description_th': 'การทดลองและค้นพบทางวิทยาศาสตร์ที่น่าตื่นเต้น',
256
+ 'description_en': 'Exciting scientific experiments and discoveries',
257
+ 'level_range': ['Beginner', 'Intermediate', 'Advanced'],
258
+ 'vocabulary': {
259
+ 'Beginner': ['experiment', 'science', 'lab', 'test', 'mix', 'observe', 'change', 'result'],
260
+ 'Intermediate': ['hypothesis', 'research', 'discovery', 'invention', 'laboratory', 'scientist'],
261
+ 'Advanced': ['innovation', 'technological', 'breakthrough', 'analysis', 'investigation']
262
+ },
263
+ 'story_starters': {
264
+ 'Beginner': [
265
+ {'th': 'การทดลองง่ายๆ เริ่มต้นด้วย...', 'en': 'The simple experiment started with...'},
266
+ {'th': 'ในห้องทดลองมีสิ่งมหัศจรรย์...', 'en': 'In the lab, there was something amazing...'},
267
+ {'th': 'เมื่อผสมสองสิ่งเข้าด้วยกัน...', 'en': 'When mixing the two things together...'}
268
+ ],
269
+ 'Intermediate': [
270
+ {'th': 'การค้นพบที่น่าประหลาดใจ...', 'en': 'A surprising discovery...'},
271
+ {'th': 'สิ่งประดิษฐ์ใหม่ทำให้...', 'en': 'The new invention made...'},
272
+ {'th': 'การทดลองที่ไม่คาดคิด...', 'en': 'An unexpected experiment...'}
273
+ ],
274
+ 'Advanced': [
275
+ {'th': 'นวัตกรรมที่จะเปลี่ยนโลก...', 'en': 'Innovation that would change the world...'},
276
+ {'th': 'การค้นพบทางวิทยาศาสตร์ครั้งสำคัญ...', 'en': 'An important scientific discovery...'},
277
+ {'th': 'เทคโนโลยีใหม่ที่น่าทึ่ง...', 'en': 'Amazing new technology...'}
278
+ ]
279
+ },
280
+ 'background_color': '#E8EAF6',
281
+ 'accent_color': '#3F51B5'
282
+ }
283
+ }
284
+
285
+ # Set up logging
286
+ logging.basicConfig(
287
+ level=logging.INFO,
288
+ format='%(asctime)s - %(levelname)s - %(message)s'
289
+ )
290
+
291
+ # === 2. CONFIGURATIONS ===
292
+ # Page Config
293
+ st.set_page_config(
294
+ page_title="JoyStory - Interactive Story Adventure",
295
+ page_icon="📖",
296
+ layout="wide",
297
+ initial_sidebar_state="collapsed"
298
+ )
299
+
300
+ # Initialize OpenAI client
301
+ try:
302
+ client = OpenAI()
303
+ except Exception as e:
304
+ logging.error(f"Failed to initialize OpenAI client: {str(e)}")
305
+ st.error("Failed to initialize AI services. Please try again later.")
306
+
307
+ # Define Constants
308
+ MAX_RETRIES = 3
309
+ DEFAULT_LEVEL = 'Beginner'
310
+ SUPPORTED_LANGUAGES = ['th', 'en']
311
+
312
+ # Achievement Requirements
313
+ ACHIEVEMENT_THRESHOLDS = {
314
+ 'perfect_writer': 5, # 5 correct sentences in a row
315
+ 'vocabulary_master': 50, # 50 unique words used
316
+ 'story_master': 10, # 10 sentences in story
317
+ 'accuracy_king': 80 # 80% accuracy rate
318
+ }
319
+
320
+ # Level Configuration
321
+ level_options = {
322
+ 'Beginner': {
323
+ 'thai_name': 'ระดับเริ่มต้น (ป.1-3)',
324
+ 'age_range': '7-9 ปี',
325
+ 'description': 'เหมาะสำหรับน้องๆ ที่เริ่มเรียนรู้การเขียนประโยคภาษาอังกฤษ',
326
+ 'features': [
327
+ 'ประโยคสั้นๆ ง่ายๆ',
328
+ 'คำศัพท์พื้นฐานที่ใช้ในชีวิตประจำวัน',
329
+ 'มีคำแนะนำภาษาไทยละเอียด',
330
+ 'เน้นการใช้ Present Simple Tense'
331
+ ],
332
+ 'max_sentence_length': 10,
333
+ 'allowed_tenses': ['present_simple'],
334
+ 'feedback_level': 'detailed'
335
+ },
336
+ 'Intermediate': {
337
+ 'thai_name': 'ระดับกลาง (ป.4-6)',
338
+ 'age_range': '10-12 ปี',
339
+ 'description': 'เหมาะสำหรับน้องๆ ที่สามารถเขียนประโยคพื้นฐานได้แล้ว',
340
+ 'features': [
341
+ 'ประโยคซับซ้อนขึ้น',
342
+ 'เริ่มใช้ Past Tense ได้',
343
+ 'คำศัพท์หลากหลายขึ้น',
344
+ 'สามารถเขียนเรื่องราวต่อเนื่องได้'
345
+ ],
346
+ 'max_sentence_length': 15,
347
+ 'allowed_tenses': ['present_simple', 'past_simple'],
348
+ 'feedback_level': 'moderate'
349
+ },
350
+ 'Advanced': {
351
+ 'thai_name': 'ระดับก้าวหน้า (ม.1-3)',
352
+ 'age_range': '13-15 ปี',
353
+ 'description': 'เหมาะสำหรับน้องๆ ที่มีพื้นฐานภาษาอังกฤษดี',
354
+ 'features': [
355
+ 'เขียนเรื่องราวได้หลากหลายรูปแบบ',
356
+ 'ใช้ Tense ต่างๆ ได้',
357
+ 'คำศัพท์ระดับสูงขึ้น',
358
+ 'สามารถแต่งเรื่องที่ซับซ้อนได้'
359
+ ],
360
+ 'max_sentence_length': 20,
361
+ 'allowed_tenses': ['present_simple', 'past_simple', 'present_perfect', 'past_perfect'],
362
+ 'feedback_level': 'concise'
363
+ }
364
+ }
365
+
366
+ # Achievement Configuration
367
+ achievements_list = {
368
+ 'perfect_writer': {
369
+ 'name': '🌟 นักเขียนไร้ที่ติ',
370
+ 'description': 'เขียนถูกต้อง 5 ประโยคติดต่อกัน',
371
+ 'condition': lambda: st.session_state.points['streak'] >= ACHIEVEMENT_THRESHOLDS['perfect_writer']
372
+ },
373
+ 'vocabulary_master': {
374
+ 'name': '📚 ราชาคำศัพท์',
375
+ 'description': 'ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ',
376
+ 'condition': lambda: len(st.session_state.stats['vocabulary_used']) >= ACHIEVEMENT_THRESHOLDS['vocabulary_master']
377
+ },
378
+ 'story_master': {
379
+ 'name': '📖 นักแต่งนิทาน',
380
+ 'description': 'เขียนเรื่องยาว 10 ประโยค',
381
+ 'condition': lambda: len(st.session_state.story) >= ACHIEVEMENT_THRESHOLDS['story_master']
382
+ },
383
+ 'accuracy_king': {
384
+ 'name': '👑 ราชาความแม่นยำ',
385
+ 'description': 'มีอัตราความถูกต้อง 80% ขึ้นไป (อย่างน้อย 10 ประโยค)',
386
+ 'condition': lambda: (
387
+ st.session_state.stats['total_sentences'] >= 10 and
388
+ st.session_state.stats['accuracy_rate'] >= ACHIEVEMENT_THRESHOLDS['accuracy_king']
389
+ )
390
+ }
391
+ }
392
+
393
+ # Initial CSS Setup
394
+ st.markdown("""
395
+ <style>
396
+ /* Base Styles */
397
+ @import url('https://fonts.googleapis.com/css2?family=Sarabun:wght@400;600&display=swap');
398
+
399
+ body {
400
+ font-family: 'Sarabun', sans-serif;
401
+ }
402
+
403
+ /* Custom Classes */
404
+ .thai-eng {
405
+ font-size: 1.1em;
406
+ padding: 10px;
407
+ background-color: #f8f9fa;
408
+ border-radius: 8px;
409
+ margin: 10px 0;
410
+ }
411
+
412
+ .theme-card {
413
+ transition: all 0.3s ease;
414
+ }
415
+
416
+ .theme-card:hover {
417
+ transform: translateY(-5px);
418
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
419
+ }
420
+
421
+ .theme-header {
422
+ text-align: center;
423
+ margin-bottom: 2rem;
424
+ }
425
+
426
+ .theme-header h2 {
427
+ font-size: 2rem;
428
+ color: #1e88e5;
429
+ margin-bottom: 1rem;
430
+ }
431
+
432
+ .theme-header p {
433
+ color: #666;
434
+ font-size: 1.1rem;
435
+ }
436
+
437
+ /* ปรับแต่งปุ่มเลือกธีม */
438
+ .stButton > button {
439
+ width: 100%;
440
+ padding: 0.75rem;
441
+ border: none;
442
+ border-radius: 8px;
443
+ font-family: 'Sarabun', sans-serif;
444
+ transition: all 0.2s ease;
445
+ }
446
+
447
+ .stButton > button:hover {
448
+ transform: translateY(-2px);
449
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
450
+ }
451
+
452
+ .thai {
453
+ color: #1e88e5;
454
+ }
455
+
456
+ .eng {
457
+ color: #333;
458
+ }
459
+
460
+ /* Error Messages */
461
+ .error-message {
462
+ color: #d32f2f;
463
+ padding: 10px;
464
+ border-left: 4px solid #d32f2f;
465
+ background-color: #ffebee;
466
+ margin: 10px 0;
467
+ }
468
+
469
+ /* Success Messages */
470
+ .success-message {
471
+ color: #2e7d32;
472
+ padding: 10px;
473
+ border-left: 4px solid #2e7d32;
474
+ background-color: #e8f5e9;
475
+ margin: 10px 0;
476
+ }
477
+ </style>
478
+ """, unsafe_allow_html=True)
479
+
480
+ # Reset counter when page reloads
481
+ if 'theme_button_counter' in st.session_state:
482
+ st.session_state.theme_button_counter = 0
483
+
484
+ # === 3. STATE MANAGEMENT ===
485
+ def init_session_state():
486
+ """Initialize all session state variables with default values"""
487
+
488
+ # Basic states
489
+ default_states = {
490
+ 'theme_selection_id': datetime.now().strftime('%Y%m%d%H%M%S'),
491
+ 'current_theme': None,
492
+ 'theme_button_counter': 0,
493
+ 'theme_story_starter': None,
494
+ 'story': [],
495
+ 'feedback': None,
496
+ 'level': DEFAULT_LEVEL,
497
+ 'should_reset': False,
498
+ 'last_interaction': datetime.now().isoformat(),
499
+ }
500
+
501
+ # Points system
502
+ points_states = {
503
+ 'points': {
504
+ 'total': 0,
505
+ 'perfect_sentences': 0,
506
+ 'corrections_made': 0,
507
+ 'streak': 0,
508
+ 'max_streak': 0
509
+ }
510
+ }
511
+
512
+ # Statistics tracking
513
+ stats_states = {
514
+ 'stats': {
515
+ 'total_sentences': 0,
516
+ 'correct_first_try': 0,
517
+ 'accuracy_rate': 0.0,
518
+ 'vocabulary_used': set(),
519
+ 'corrections_made': 0,
520
+ 'average_sentence_length': 0,
521
+ 'total_words': 0,
522
+ 'session_duration': 0
523
+ }
524
+ }
525
+
526
+ # Game progress
527
+ progress_states = {
528
+ 'achievements': [],
529
+ 'unlocked_features': set(),
530
+ 'current_milestone': 0,
531
+ 'next_milestone': 5
532
+ }
533
+
534
+ # User preferences
535
+ preferences_states = {
536
+ 'language': 'th',
537
+ 'feedback_level': 'detailed',
538
+ 'theme_color': 'light',
539
+ 'sound_enabled': True
540
+ }
541
+
542
+ # Initialize all states if they don't exist
543
+ for state_dict in [default_states, points_states, stats_states, progress_states, preferences_states]:
544
+ for key, value in state_dict.items():
545
+ if key not in st.session_state:
546
+ st.session_state[key] = value
547
+
548
+ if 'clear_input' not in st.session_state:
549
+ st.session_state.clear_input = False
550
+ if 'text_input' not in st.session_state:
551
+ st.session_state.text_input = ""
552
+
553
+ def init_theme_state():
554
+ """Initialize theme-specific state variables"""
555
+ if 'current_theme' not in st.session_state:
556
+ st.session_state.current_theme = None
557
+ if 'theme_story_starter' not in st.session_state:
558
+ st.session_state.theme_story_starter = None
559
+
560
+ def reset_story():
561
+ """Reset story and related state variables"""
562
+ try:
563
+ # Reset story-related states
564
+ st.session_state.story = []
565
+ st.session_state.feedback = None
566
+ st.session_state.theme_story_starter = None
567
+
568
+ # Reset points
569
+ st.session_state.points = {
570
+ 'total': 0,
571
+ 'perfect_sentences': 0,
572
+ 'corrections_made': 0,
573
+ 'streak': 0,
574
+ 'max_streak': 0
575
+ }
576
+
577
+ # Reset stats
578
+ st.session_state.stats = {
579
+ 'total_sentences': 0,
580
+ 'correct_first_try': 0,
581
+ 'accuracy_rate': 0.0,
582
+ 'vocabulary_used': set(),
583
+ 'corrections_made': 0,
584
+ 'average_sentence_length': 0,
585
+ 'total_words': 0,
586
+ 'session_duration': 0
587
+ }
588
+
589
+ # Reset progress
590
+ st.session_state.current_milestone = 0
591
+ st.session_state.next_milestone = 5
592
+
593
+ # Reset flag
594
+ st.session_state.should_reset = False
595
+
596
+ # Log reset
597
+ logging.info("Story state reset successfully")
598
+
599
+ return True
600
+
601
+ except Exception as e:
602
+ logging.error(f"Error resetting story state: {str(e)}")
603
+ st.error("เกิดข้อผิดพลาดในการรีเซ็ตเรื่อง กรุณาลองใหม่อีกครั้ง")
604
+ return False
605
+
606
+ def save_progress() -> Dict:
607
+ """Save current progress to JSON format"""
608
+ try:
609
+ progress_data = {
610
+ 'timestamp': datetime.now().isoformat(),
611
+ 'level': st.session_state.level,
612
+ 'story': st.session_state.story,
613
+ 'stats': {
614
+ key: list(value) if isinstance(value, set) else value
615
+ for key, value in st.session_state.stats.items()
616
+ },
617
+ 'points': st.session_state.points,
618
+ 'achievements': st.session_state.achievements,
619
+ 'current_theme': st.session_state.current_theme
620
+ }
621
+
622
+ logging.info("Progress saved successfully")
623
+ return progress_data
624
+
625
+ except Exception as e:
626
+ logging.error(f"Error saving progress: {str(e)}")
627
+ raise
628
+
629
+ def load_progress(data: Dict):
630
+ """Load progress from saved data"""
631
+ try:
632
+ # Validate data structure
633
+ required_keys = ['level', 'story', 'stats', 'points', 'achievements']
634
+ if not all(key in data for key in required_keys):
635
+ raise ValueError("Invalid save data format")
636
+
637
+ # Load basic data
638
+ st.session_state.level = data['level']
639
+ st.session_state.story = data['story']
640
+ st.session_state.achievements = data['achievements']
641
+ st.session_state.points = data['points']
642
+
643
+ # Load stats (converting lists back to sets where needed)
644
+ st.session_state.stats = {
645
+ 'total_sentences': data['stats']['total_sentences'],
646
+ 'correct_first_try': data['stats']['correct_first_try'],
647
+ 'accuracy_rate': data['stats']['accuracy_rate'],
648
+ 'vocabulary_used': set(data['stats']['vocabulary_used']),
649
+ 'corrections_made': data['stats']['corrections_made'],
650
+ 'average_sentence_length': data['stats'].get('average_sentence_length', 0),
651
+ 'total_words': data['stats'].get('total_words', 0),
652
+ 'session_duration': data['stats'].get('session_duration', 0)
653
+ }
654
+
655
+ # Load theme if present
656
+ if 'current_theme' in data:
657
+ st.session_state.current_theme = data['current_theme']
658
+
659
+ logging.info("Progress loaded successfully")
660
+ st.success("โหลดความก้าวหน้าเรียบร้อย!")
661
+
662
+ return True
663
+
664
+ except Exception as e:
665
+ logging.error(f"Error loading progress: {str(e)}")
666
+ st.error("เกิดข้อผิดพลาดในการโหลดข้อมูล กรุณาตรวจสอบไฟล์และลองใหม่อีกครั้ง")
667
+ return False
668
+
669
+ def update_session_stats():
670
+ """Update session statistics"""
671
+ try:
672
+ if st.session_state.story:
673
+ # Update word counts
674
+ all_text = ' '.join([entry['content'] for entry in st.session_state.story])
675
+ words = all_text.split()
676
+ st.session_state.stats['total_words'] = len(words)
677
+
678
+ # Update average sentence length
679
+ if st.session_state.stats['total_sentences'] > 0:
680
+ st.session_state.stats['average_sentence_length'] = (
681
+ st.session_state.stats['total_words'] /
682
+ st.session_state.stats['total_sentences']
683
+ )
684
+
685
+ # Update session duration
686
+ start_time = datetime.fromisoformat(st.session_state.last_interaction)
687
+ current_time = datetime.now()
688
+ duration = (current_time - start_time).total_seconds()
689
+ st.session_state.stats['session_duration'] = duration
690
+
691
+ # Update last interaction time
692
+ st.session_state.last_interaction = current_time.isoformat()
693
+
694
+ return True
695
+
696
+ except Exception as e:
697
+ logging.error(f"Error updating session stats: {str(e)}")
698
+ return False
699
+
700
+ # === 4. UTILITY FUNCTIONS ===
701
+ def generate_story_continuation(user_input: str, level: str) -> str:
702
+ """Generate AI story continuation using ChatGPT"""
703
+
704
+ level_context = {
705
+ 'Beginner': {
706
+ 'instructions': """
707
+ Role: Teaching assistant for Thai students (grades 1-3)
708
+ Rules:
709
+ - Use only 1-2 VERY simple sentences
710
+ - Use Present Simple Tense only
711
+ - Basic vocabulary (family, school, daily activities)
712
+ - 5-7 words per sentence maximum
713
+ - Focus on clear, basic responses
714
+ """,
715
+ 'max_tokens': 30,
716
+ 'temperature': 0.6
717
+ },
718
+ 'Intermediate': {
719
+ 'instructions': """
720
+ Role: Teaching assistant for Thai students (grades 4-6)
721
+ Rules:
722
+ - Use exactly 2 sentences maximum
723
+ - Can use Present or Past Tense
724
+ - Keep each sentence under 12 words
725
+ - Grade-appropriate vocabulary
726
+ - Add simple descriptions but stay concise
727
+ """,
728
+ 'max_tokens': 40,
729
+ 'temperature': 0.7
730
+ },
731
+ 'Advanced': {
732
+ 'instructions': """
733
+ Role: Teaching assistant for Thai students (grades 7-9)
734
+ Rules:
735
+ - Use 2-3 sentences maximum
736
+ - Various tenses allowed
737
+ - Natural sentence length but keep overall response concise
738
+ - More sophisticated vocabulary and structures
739
+ - Create engaging responses that encourage creative continuation
740
+ """,
741
+ 'max_tokens': 50,
742
+ 'temperature': 0.8
743
+ }
744
+ }
745
+
746
+ try:
747
+ # Get recent story context
748
+ story_context = '\n'.join([
749
+ entry['content'] for entry in st.session_state.story[-3:]
750
+ ]) if st.session_state.story else "Story just started"
751
+
752
+ # Create prompt
753
+ level_settings = level_context[level]
754
+
755
+ for _ in range(MAX_RETRIES):
756
+ try:
757
+ response = client.chat.completions.create(
758
+ model="gpt-4o-mini",
759
+ messages=[
760
+ {
761
+ "role": "system",
762
+ "content": f"""
763
+ {level_settings['instructions']}
764
+
765
+ CRUCIAL GUIDELINES:
766
+ - Never exceed the maximum sentences for the level
767
+ - Create openings for student's creativity
768
+ - Do not resolve plot points or conclude the story
769
+ - Avoid using 'suddenly' or 'then'
770
+ - Make each sentence meaningful but incomplete
771
+ - Leave room for the student to develop the story
772
+ """
773
+ },
774
+ {
775
+ "role": "user",
776
+ "content": f"Story context:\n{story_context}\nStudent's input:\n{user_input}\nProvide a brief continuation:"
777
+ }
778
+ ],
779
+ max_tokens=level_settings['max_tokens'],
780
+ temperature=level_settings['temperature'],
781
+ presence_penalty=0.6,
782
+ frequency_penalty=0.6
783
+ )
784
+
785
+ # Process and clean response
786
+ response_text = response.choices[0].message.content.strip()
787
+ sentences = [s.strip() for s in response_text.split('.') if s.strip()]
788
+
789
+ # Limit sentences based on level
790
+ max_sentences = {'Beginner': 2, 'Intermediate': 2, 'Advanced': 3}
791
+ if len(sentences) > max_sentences[level]:
792
+ sentences = sentences[:max_sentences[level]]
793
+
794
+ # Reconstruct response
795
+ final_response = '. '.join(sentences) + '.'
796
+
797
+ logging.info(f"Generated continuation for level {level}")
798
+ return final_response
799
+
800
+ except Exception as e:
801
+ if _ < MAX_RETRIES - 1:
802
+ logging.warning(f"Retry {_+1} failed: {str(e)}")
803
+ continue
804
+ raise
805
+
806
+ raise Exception("Max retries exceeded")
807
+
808
+ except Exception as e:
809
+ logging.error(f"Error generating story continuation: {str(e)}")
810
+ return "I'm having trouble continuing the story. Please try again."
811
+
812
+ def generate_dynamic_story_starter(theme_id: str, level: str) -> Dict[str, str]:
813
+ """
814
+ Dynamically generate a story starter based on theme and level.
815
+ Returns both Thai and English versions.
816
+ """
817
+ try:
818
+ # Get theme data
819
+ theme = story_themes.get(theme_id)
820
+ if not theme:
821
+ raise ValueError(f"Theme {theme_id} not found")
822
+
823
+ # Get random starter for the level
824
+ level_starters = theme['story_starters'].get(level, [])
825
+ if not level_starters:
826
+ # Fallback to Beginner level if no starters found for specified level
827
+ level_starters = theme['story_starters'].get('Beginner', [])
828
+
829
+ if not level_starters:
830
+ raise ValueError(f"No story starters found for theme {theme_id}")
831
+
832
+ # Select random starter
833
+ starter = random.choice(level_starters)
834
+
835
+ # Return both languages
836
+ return {
837
+ 'en': starter['en'],
838
+ 'th': starter['th']
839
+ }
840
+
841
+ except Exception as e:
842
+ logging.error(f"Error generating story starter: {str(e)}")
843
+ # Provide fallback starter
844
+ return {
845
+ 'en': 'Once upon a time...',
846
+ 'th': 'กาลครั้งหนึ่ง...'
847
+ }
848
+
849
+ def update_points(is_correct_first_try: bool):
850
+ """อัพเดตคะแนนตามผลการเขียน"""
851
+ try:
852
+ # คำนวณคะแนนพื้นฐาน
853
+ base_points = 10
854
+
855
+ if is_correct_first_try:
856
+ # ถูกต้องในครั้งแรก
857
+ points = base_points * 2
858
+ st.session_state.points['perfect_sentences'] += 1
859
+ st.session_state.points['streak'] += 1
860
+
861
+ # อัพเดต max streak
862
+ if st.session_state.points['streak'] > st.session_state.points['max_streak']:
863
+ st.session_state.points['max_streak'] = st.session_state.points['streak']
864
+ else:
865
+ # ต้องแก้ไข
866
+ points = base_points // 2
867
+ st.session_state.points['corrections_made'] += 1
868
+ st.session_state.points['streak'] = 0
869
+
870
+ # เพิ่มคะแนนรวม
871
+ st.session_state.points['total'] += points
872
+
873
+ # อัพเดตสถิติ
874
+ st.session_state.stats['total_sentences'] += 1
875
+ if is_correct_first_try:
876
+ st.session_state.stats['correct_first_try'] += 1
877
+
878
+ # คำนวณอัตราความถูกต้อง
879
+ st.session_state.stats['accuracy_rate'] = (
880
+ st.session_state.stats['correct_first_try'] /
881
+ st.session_state.stats['total_sentences'] * 100
882
+ )
883
+
884
+ logging.info(f"Points updated: +{points} points")
885
+ return True
886
+
887
+ except Exception as e:
888
+ logging.error(f"Error updating points: {str(e)}")
889
+ return False
890
+
891
+ def apply_correction(story_index: int, corrected_text: str):
892
+ """แก้ไขประโยคในเรื่อง"""
893
+ try:
894
+ if not (0 <= story_index < len(st.session_state.story)):
895
+ raise ValueError("Invalid story index")
896
+
897
+ # เก็บข้อความเดิม
898
+ original_text = st.session_state.story[story_index]['content']
899
+
900
+ # อัพเดทข้อความ
901
+ st.session_state.story[story_index].update({
902
+ 'content': corrected_text,
903
+ 'is_corrected': True,
904
+ 'is_correct': True,
905
+ 'original_text': original_text,
906
+ 'correction_timestamp': datetime.now().isoformat()
907
+ })
908
+
909
+ # อัพเดทสถิติ
910
+ st.session_state.stats['corrections_made'] += 1
911
+
912
+ # Log การแก้ไข
913
+ logging.info(f"Sentence corrected at index {story_index}")
914
+
915
+ # แสดงข้อความยืนยัน
916
+ st.success("✅ แก้ไขประโยคเรียบร้อยแล้ว!")
917
+ return True
918
+
919
+ except Exception as e:
920
+ logging.error(f"Error applying correction: {str(e)}")
921
+ st.error("เกิดข้อผิดพลาดในการแก้ไขประโยค กรุณาลองใหม่อีกครั้ง")
922
+ return False
923
+
924
+ def update_achievements():
925
+ """ตรวจสอบและอัพเดตความสำเร็จ"""
926
+ try:
927
+ current_achievements = st.session_state.achievements
928
+
929
+ # ตรวจสอบเงื่อนไขต่างๆ
930
+ if (st.session_state.points['streak'] >= 5 and
931
+ "🌟 นักเขียนไร้ที่ติ" not in current_achievements):
932
+ current_achievements.append("🌟 นักเขียนไร้ที่ติ")
933
+ st.success("🎉 ได้รับความสำเร็จใหม่: นักเขียนไร้ที่ติ!")
934
+
935
+ if (len(st.session_state.stats['vocabulary_used']) >= 50 and
936
+ "📚 ราชาคำศัพท์" not in current_achievements):
937
+ current_achievements.append("📚 ราชาคำศัพท์")
938
+ st.success("🎉 ได้รับความสำเร็จใหม่: ราชาคำศัพท์!")
939
+
940
+ if (len(st.session_state.story) >= 10 and
941
+ "📖 นักแต่งนิทาน" not in current_achievements):
942
+ current_achievements.append("📖 นักแต่งนิทาน")
943
+ st.success("🎉 ได้รับความสำเร็จใหม่: นักแต่งนิทาน!")
944
+
945
+ if (st.session_state.stats['total_sentences'] >= 10 and
946
+ st.session_state.stats['accuracy_rate'] >= 80 and
947
+ "👑 ราชาความแม่นยำ" not in current_achievements):
948
+ current_achievements.append("👑 ราชาความแม่นยำ")
949
+ st.success("🎉 ได้รับความสำเร็จใหม่: ราชาความแม่นยำ!")
950
+
951
+ # บันทึกความสำเร็จ
952
+ st.session_state.achievements = current_achievements
953
+ logging.info("Achievements updated successfully")
954
+ return True
955
+
956
+ except Exception as e:
957
+ logging.error(f"Error updating achievements: {str(e)}")
958
+ return False
959
+
960
+ def provide_feedback(text: str, level: str) -> Dict[str, str]:
961
+ """Provide feedback on the user's writing with appropriate level"""
962
+
963
+ level_prompts = {
964
+ 'Beginner': """
965
+ Focus on:
966
+ - Basic sentence structure (Subject + Verb + Object)
967
+ - Present Simple Tense usage
968
+ - Basic vocabulary
969
+ - Capitalization and periods
970
+ Provide very simple, encouraging feedback in Thai.
971
+ """,
972
+ 'Intermediate': """
973
+ Focus on:
974
+ - Sentence variety
975
+ - Past Tense usage
976
+ - Vocabulary appropriateness
977
+ - Basic punctuation
978
+ - Simple conjunctions
979
+ Provide moderately detailed feedback in Thai.
980
+ """,
981
+ 'Advanced': """
982
+ Focus on:
983
+ - Complex sentence structures
984
+ - Various tense usage
985
+ - Advanced vocabulary
986
+ - All punctuation
987
+ - Style and flow
988
+ Provide comprehensive feedback in Thai.
989
+ """
990
+ }
991
+
992
+ try:
993
+ for _ in range(MAX_RETRIES):
994
+ try:
995
+ response = client.chat.completions.create(
996
+ model="gpt-4o-mini",
997
+ messages=[
998
+ {
999
+ "role": "system",
1000
+ "content": f"""
1001
+ You are a Thai English teacher helping {level} students.
1002
+ {level_prompts[level]}
1003
+
1004
+ Return your response in this EXACT format (valid JSON):
1005
+ {{
1006
+ "feedback": "(ข้อเสนอแนะภาษาไทย)",
1007
+ "corrected": "(ประโยคภาษาอังกฤษที่ถูกต้อง)",
1008
+ "has_errors": true/false,
1009
+ "error_types": ["grammar", "vocabulary", "spelling", ...],
1010
+ "difficulty_score": 1-10
1011
+ }}
1012
+ """
1013
+ },
1014
+ {
1015
+ "role": "user",
1016
+ "content": f"Review this sentence: {text}"
1017
+ }
1018
+ ],
1019
+ max_tokens=200,
1020
+ temperature=0.3
1021
+ )
1022
+
1023
+ # Parse response
1024
+ feedback_data = json.loads(response.choices[0].message.content.strip())
1025
+
1026
+ # Validate required fields
1027
+ required_fields = ['feedback', 'corrected', 'has_errors']
1028
+ if not all(field in feedback_data for field in required_fields):
1029
+ raise ValueError("Missing required fields in feedback")
1030
+
1031
+ logging.info(f"Generated feedback for level {level}")
1032
+ return feedback_data
1033
+
1034
+ except json.JSONDecodeError:
1035
+ if _ < MAX_RETRIES - 1:
1036
+ continue
1037
+ raise
1038
+
1039
+ raise Exception("Max retries exceeded")
1040
+
1041
+ except Exception as e:
1042
+ logging.error(f"Error generating feedback: {str(e)}")
1043
+ return {
1044
+ "feedback": "⚠️ ระบบไม่สามารถวิเคราะห์ประโยคได้ กรุณาลองใหม่อีกครั้ง",
1045
+ "corrected": text,
1046
+ "has_errors": False,
1047
+ "error_types": [],
1048
+ "difficulty_score": 5
1049
+ }
1050
+
1051
+ def get_vocabulary_suggestions(context: str = "", level: str = DEFAULT_LEVEL) -> List[str]:
1052
+ """Get contextual vocabulary suggestions with Thai translations"""
1053
+ try:
1054
+ recent_story = context or '\n'.join([
1055
+ entry['content'] for entry in st.session_state.story[-3:]
1056
+ ]) if st.session_state.story else "Story just started"
1057
+
1058
+ response = client.chat.completions.create(
1059
+ model="gpt-4o-mini",
1060
+ messages=[
1061
+ {
1062
+ "role": "system",
1063
+ "content": f"""
1064
+ You are a Thai-English bilingual teacher.
1065
+ Suggest 5 English words appropriate for {level} level students.
1066
+ Format each suggestion as:
1067
+ word (type) - คำแปล | example sentence
1068
+ """
1069
+ },
1070
+ {
1071
+ "role": "user",
1072
+ "content": f"Story context:\n{recent_story}\n\nSuggest relevant words:"
1073
+ }
1074
+ ],
1075
+ max_tokens=200,
1076
+ temperature=0.8
1077
+ )
1078
+
1079
+ suggestions = response.choices[0].message.content.split('\n')
1080
+ return [s.strip() for s in suggestions if s.strip()]
1081
+
1082
+ except Exception as e:
1083
+ logging.error(f"Error getting vocabulary suggestions: {str(e)}")
1084
+ return [
1085
+ "happy (adj) - มีความสุข | I am happy today",
1086
+ "run (verb) - วิ่ง | The dog runs fast",
1087
+ "tree (noun) - ต้นไม้ | A tall tree"
1088
+ ]
1089
+
1090
+ def get_creative_prompt() -> Dict[str, str]:
1091
+ """Generate a bilingual creative writing prompt"""
1092
+ try:
1093
+ response = client.chat.completions.create(
1094
+ model="gpt-4o-mini",
1095
+ messages=[
1096
+ {
1097
+ "role": "system",
1098
+ "content": """
1099
+ Create short story prompts in both English and Thai.
1100
+ Keep it simple and under 6 words each.
1101
+ Make it open-ended and encouraging.
1102
+ """
1103
+ },
1104
+ {
1105
+ "role": "user",
1106
+ "content": "Generate a creative writing prompt:"
1107
+ }
1108
+ ],
1109
+ max_tokens=50,
1110
+ temperature=0.7
1111
+ )
1112
+
1113
+ prompt_eng = response.choices[0].message.content.strip()
1114
+
1115
+ # Get Thai translation
1116
+ response_thai = client.chat.completions.create(
1117
+ model="gpt-4o-mini",
1118
+ messages=[
1119
+ {
1120
+ "role": "system",
1121
+ "content": "Translate to short Thai prompt, keep it natural:"
1122
+ },
1123
+ {
1124
+ "role": "user",
1125
+ "content": prompt_eng
1126
+ }
1127
+ ],
1128
+ max_tokens=50,
1129
+ temperature=0.7
1130
+ )
1131
+
1132
+ prompt_thai = response_thai.choices[0].message.content.strip()
1133
+
1134
+ return {
1135
+ "eng": prompt_eng,
1136
+ "thai": prompt_thai
1137
+ }
1138
+
1139
+ except Exception as e:
1140
+ logging.error(f"Error generating creative prompt: {str(e)}")
1141
+ return {
1142
+ "eng": "What happens next?",
1143
+ "thai": "แล้วอะไรจะเกิดขึ้นต่อ?"
1144
+ }
1145
+
1146
+ def calculate_text_metrics(text: str) -> Dict[str, float]:
1147
+ """Calculate various metrics for the given text"""
1148
+ try:
1149
+ words = text.split()
1150
+ sentences = [s.strip() for s in text.split('.') if s.strip()]
1151
+
1152
+ metrics = {
1153
+ 'word_count': len(words),
1154
+ 'sentence_count': len(sentences),
1155
+ 'average_word_length': sum(len(word) for word in words) / len(words) if words else 0,
1156
+ 'unique_words': len(set(words)),
1157
+ 'complexity_score': calculate_complexity_score(text)
1158
+ }
1159
+
1160
+ return metrics
1161
+
1162
+ except Exception as e:
1163
+ logging.error(f"Error calculating text metrics: {str(e)}")
1164
+ return {
1165
+ 'word_count': 0,
1166
+ 'sentence_count': 0,
1167
+ 'average_word_length': 0,
1168
+ 'unique_words': 0,
1169
+ 'complexity_score': 0
1170
+ }
1171
+
1172
+ def calculate_complexity_score(text: str) -> float:
1173
+ """Calculate a complexity score for the text (0-10)"""
1174
+ try:
1175
+ # Basic metrics
1176
+ words = text.split()
1177
+ word_count = len(words)
1178
+ unique_words = len(set(words))
1179
+ avg_word_length = sum(len(word) for word in words) / word_count if word_count > 0 else 0
1180
+
1181
+ # Calculate score components
1182
+ vocabulary_score = (unique_words / word_count) * 5 if word_count > 0 else 0
1183
+ length_score = min((avg_word_length / 10) * 5, 5)
1184
+
1185
+ # Combine scores
1186
+ total_score = vocabulary_score + length_score
1187
+
1188
+ return min(total_score, 10.0)
1189
+
1190
+ except Exception as e:
1191
+ logging.error(f"Error calculating complexity score: {str(e)}")
1192
+ return 5.0
1193
+
1194
+ # === 5. UI COMPONENTS ===
1195
+ def show_welcome_section():
1196
+ """Display welcome message and introduction"""
1197
+ st.markdown("""
1198
+ <div class="welcome-header" style="text-align: center; padding: 20px;">
1199
+ <div class="thai" style="font-size: 1.5em; margin-bottom: 10px;">
1200
+ 🌟 ยินดีต้อนรับสู่ JoyStory - แอพฝึกเขียนภาษาอังกฤษแสนสนุก!
1201
+ </div>
1202
+ <div style="color: #666; margin-bottom: 20px;">
1203
+ เรียนรู้ภาษาอังกฤษผ่านการเขียนเรื่องราวด้วยตัวเอง
1204
+ พร้อมผู้ช่วย AI ที่จะช่วยแนะนำและให้กำลังใจ
1205
+ </div>
1206
+ <div class="eng" style="font-style: italic; color: #1e88e5;">
1207
+ Welcome to JoyStory - Fun English Writing Adventure!
1208
+ </div>
1209
+ </div>
1210
+ """, unsafe_allow_html=True)
1211
+
1212
+ def show_parent_guide():
1213
+ """Display guide for parents"""
1214
+ with st.expander("📖 คำแนะนำสำหรับผู้ปกครอง | Parent's Guide"):
1215
+ st.markdown("""
1216
+ <div class="parent-guide" style="background-color: #fff3e0; padding: 20px; border-radius: 10px;">
1217
+ <h4 style="color: #f57c00; margin-bottom: 15px;">คำแนะนำในการใช้งาน</h4>
1218
+ <ul style="list-style-type: none; padding-left: 0;">
1219
+ <li style="margin-bottom: 10px;">
1220
+ 👥 <strong>การมีส่วนร่วม:</strong> แนะนำให้นั่งเขียนเรื่องราวร่วมกับน้องๆ
1221
+ </li>
1222
+ <li style="margin-bottom: 10px;">
1223
+ 💡 <strong>การช่วยเหลือ:</strong> ช่วยอธิบายคำแนะนำและคำศัพท์ที่น้องๆ ไม่เข้าใจ
1224
+ </li>
1225
+ <li style="margin-bottom: 10px;">
1226
+ 🌟 <strong>การให้กำลังใจ:</strong> ให้กำลังใจและชื่นชมเมื่อน้องๆ เขียนได้ดี
1227
+ </li>
1228
+ <li style="margin-bottom: 10px;">
1229
+ ⏱️ <strong>เวลาที่เหมาะสม:</strong> ใช้เวลาในการเขียนแต่ละครั้งไม่เกิน 20-30 นาที
1230
+ </li>
1231
+ </ul>
1232
+ <div style="background-color: #ffe0b2; padding: 15px; border-radius: 8px; margin-top: 15px;">
1233
+ <p style="margin: 0;">
1234
+ 💡 <strong>เคล็ดลับ:</strong> ให้น้องๆ พูดเรื่องราวที่อยากเขียนเป็นภาษาไทยก่อน
1235
+ แล้วค่อยๆ ช่วยกันแปลงเป็นภาษาอังกฤษ
1236
+ </p>
1237
+ </div>
1238
+ </div>
1239
+ """, unsafe_allow_html=True)
1240
+
1241
+ def show_level_selection():
1242
+ """Display level selection interface"""
1243
+ st.markdown("""
1244
+ <div class="thai" style="margin-bottom: 15px;">
1245
+ 🎯 เลือกระดับการเรียนรู้
1246
+ </div>
1247
+ """, unsafe_allow_html=True)
1248
+
1249
+ level = st.radio(
1250
+ "ระดับการเรียน",
1251
+ options=list(level_options.keys()),
1252
+ format_func=lambda x: level_options[x]['thai_name'],
1253
+ key="level_selector",
1254
+ label_visibility="collapsed"
1255
+ )
1256
+
1257
+ # Show level details
1258
+ st.markdown(f"""
1259
+ <div class="level-info" style="background-color: #e3f2fd; padding: 15px; border-radius: 10px; margin: 15px 0;">
1260
+ <h4 style="color: #1976d2; margin-bottom: 10px;">
1261
+ {level_options[level]['thai_name']}
1262
+ </h4>
1263
+ <p style="color: #444; margin-bottom: 10px;">
1264
+ 🎯 {level_options[level]['description']}
1265
+ </p>
1266
+ <p style="color: #666;">
1267
+ 👥 เหมาะสำหรับอายุ: {level_options[level]['age_range']}
1268
+ </p>
1269
+ <div style="margin-top: 15px;">
1270
+ <h5 style="color: #1976d2; margin-bottom: 8px;">✨ คุณลักษณะ:</h5>
1271
+ <ul style="list-style-type: none; padding-left: 0;">
1272
+ {' '.join([f'<li style="margin-bottom: 5px;">• {feature}</li>' for feature in level_options[level]['features']])}
1273
+ </ul>
1274
+ </div>
1275
+ </div>
1276
+ """, unsafe_allow_html=True)
1277
+
1278
+ return level
1279
+
1280
+ def show_theme_selection():
1281
+ """Display theme selection interface"""
1282
+ # Header section
1283
+ header_html = '''
1284
+ <div style="text-align: center; margin-bottom: 2rem;">
1285
+ <h2 style="color: #1e88e5; font-family: 'Sarabun', sans-serif; margin: 0; display: flex; align-items: center; justify-content: center; gap: 8px;">
1286
+ <span style="font-size: 1.5em;">🎨</span>
1287
+ <span>เลือกธีมเรื่องราว | Choose Story Theme</span>
1288
+ </h2>
1289
+ <p style="color: #666; max-width: 600px; margin: 1rem auto 0;">
1290
+ เลือกโลกแห่งจินตนาการที่คุณต้องการผจญภัย และเริ่มต้นเขียนเรื่องราวของคุณ
1291
+ </p>
1292
+ </div>
1293
+ '''
1294
+ st.markdown(header_html, unsafe_allow_html=True)
1295
+
1296
+ # Filter available themes
1297
+ available_themes = [
1298
+ theme for theme in story_themes.values()
1299
+ if st.session_state.level in theme['level_range']
1300
+ ]
1301
+
1302
+ # Create grid layout
1303
+ num_themes = len(available_themes)
1304
+ rows = (num_themes + 3) // 4 # Ceiling division to get number of rows needed
1305
+
1306
+ # Create rows with 4 themes each
1307
+ for row in range(rows):
1308
+ cols = st.columns(4)
1309
+ for col_idx, col in enumerate(cols):
1310
+ theme_idx = row * 4 + col_idx
1311
+ if theme_idx < num_themes:
1312
+ theme = available_themes[theme_idx]
1313
+ with col:
1314
+ # Theme card container
1315
+ theme_card = f'''
1316
+ <div style="
1317
+ background-color: {theme['background_color']};
1318
+ border-radius: 15px;
1319
+ padding: 1.5rem;
1320
+ margin-bottom: 1rem;
1321
+ height: 100%;
1322
+ position: relative;
1323
+ transition: all 0.3s ease;
1324
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1325
+ ">
1326
+ <div style="position: absolute; top: 1rem; right: 1rem;
1327
+ padding: 0.25rem 0.75rem; border-radius: 999px;
1328
+ font-size: 0.8rem; background: #E3F2FD;
1329
+ color: #1E88E5;">
1330
+ {st.session_state.level}
1331
+ </div>
1332
+ <div style="font-size: 2.5rem; margin-bottom: 1rem;">
1333
+ {theme['icon']}
1334
+ </div>
1335
+ <div style="font-size: 1.2rem; font-weight: 600;
1336
+ margin-bottom: 0.5rem; color: {theme['accent_color']};">
1337
+ {theme['name_th']}
1338
+ </div>
1339
+ <div style="font-size: 0.9rem; color: #666;
1340
+ line-height: 1.4; margin-bottom: 1rem;">
1341
+ {theme['description_th']}
1342
+ </div>
1343
+ </div>
1344
+ '''
1345
+ st.markdown(theme_card, unsafe_allow_html=True)
1346
+
1347
+ # Theme selection button
1348
+ if st.button(
1349
+ f"เลือกธีม {theme['name_th']}",
1350
+ key=f"theme_{theme['id']}_{row}_{col_idx}",
1351
+ use_container_width=True
1352
+ ):
1353
+ handle_theme_selection(theme)
1354
+
1355
+ def handle_theme_selection(theme: dict):
1356
+ """Handle theme selection and initialization"""
1357
+ try:
1358
+ with st.spinner("กำลังเตรียมเรื่องราว..."):
1359
+ st.session_state.current_theme = theme['id']
1360
+ starter = generate_dynamic_story_starter(
1361
+ theme['id'],
1362
+ st.session_state.level
1363
+ )
1364
+ st.session_state.story = [{
1365
+ "role": "AI",
1366
+ "content": starter['en'],
1367
+ "thai_content": starter['th'],
1368
+ "is_starter": True
1369
+ }]
1370
+ st.success(f"เลือกธีม {theme['name_th']} เรียบร้อยแล้ว!")
1371
+ st.rerun()
1372
+ except Exception as e:
1373
+ logging.error(f"Error selecting theme: {str(e)}")
1374
+ st.error("เกิดข้อผิดพลาดในการเลือกธีม กรุณาลองใหม่อีกครั้ง")
1375
+
1376
+ def show_theme_card(theme: Dict): # เปลี่ยนจาก display_theme_card เป็น show_theme_card
1377
+ """Display a single theme card with proper styling"""
1378
+ card_html = f"""
1379
+ <div style="
1380
+ background-color: {theme['background_color']};
1381
+ border-radius: 15px;
1382
+ padding: 1.5rem;
1383
+ margin-bottom: 1rem;
1384
+ height: 100%;
1385
+ position: relative;
1386
+ transition: all 0.3s ease;
1387
+ ">
1388
+ <div style="position: absolute; top: 1rem; right: 1rem; padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.8rem; background: #E3F2FD; color: #1E88E5;">
1389
+ {st.session_state.level}
1390
+ </div>
1391
+
1392
+ <div style="font-size: 2.5rem; margin-bottom: 1rem;">
1393
+ {theme['icon']}
1394
+ </div>
1395
+
1396
+ <div style="font-size: 1.2rem; font-weight: 600; margin-bottom: 0.5rem; color: {theme['accent_color']};">
1397
+ {theme['name_th']}
1398
+ </div>
1399
+
1400
+ <div style="font-size: 0.9rem; color: #666; line-height: 1.4; margin-bottom: 1rem;">
1401
+ {theme['description_th']}
1402
+ </div>
1403
+ </div>
1404
+ """
1405
+
1406
+ # แสดงการ์ดธีม
1407
+ st.markdown(card_html, unsafe_allow_html=True)
1408
+
1409
+ # แสดงปุ่มเลือกธีม
1410
+ if st.button(
1411
+ f"เลือกธีม {theme['name_th']}",
1412
+ key=f"theme_{theme['id']}",
1413
+ help=f"คลิกเพื่อเริ่มเขียนเรื่องราวในธีม {theme['name_th']}",
1414
+ use_container_width=True
1415
+ ):
1416
+ try:
1417
+ with st.spinner("กำลังเตรียมเรื่องราว..."):
1418
+ st.session_state.current_theme = theme['id']
1419
+ starter = generate_dynamic_story_starter(
1420
+ theme['id'],
1421
+ st.session_state.level
1422
+ )
1423
+ st.session_state.story = [{
1424
+ "role": "AI",
1425
+ "content": starter['en'],
1426
+ "thai_content": starter['th'],
1427
+ "is_starter": True
1428
+ }]
1429
+ st.success(f"เลือกธีม {theme['name_th']} เรียบร้อยแล้ว!")
1430
+ st.rerun()
1431
+ except Exception as e:
1432
+ logging.error(f"Error selecting theme: {str(e)}")
1433
+ st.error("เกิดข้อผิดพลาดในการเลือกธีม กรุณาลองใหม่อีกครั้ง")
1434
+
1435
+ def show_story():
1436
+ """Display the story with proper formatting"""
1437
+ story_display = st.container()
1438
+
1439
+ with story_display:
1440
+ if not st.session_state.story:
1441
+ st.info("เลือกธีมเรื่องราวที่ต้องการเพื่อเริ่มต้นการผจญภัย!")
1442
+ return
1443
+
1444
+ # Story Display Box
1445
+ for idx, entry in enumerate(st.session_state.story):
1446
+ if entry['role'] == 'AI':
1447
+ if entry.get('is_starter'):
1448
+ # Story Starter
1449
+ st.markdown(f"""
1450
+ <div style="
1451
+ background-color: #f0f7ff;
1452
+ padding: 20px;
1453
+ border-radius: 10px;
1454
+ margin: 10px 0;
1455
+ border-left: 4px solid #1e88e5;
1456
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1457
+ ">
1458
+ <div style="color: #1e88e5; margin-bottom: 10px; font-size: 1.1em;">
1459
+ 🎬 เริ่มเรื่อง:
1460
+ </div>
1461
+ <div style="color: #666; margin-bottom: 8px; font-family: 'Sarabun', sans-serif;">
1462
+ {entry.get('thai_content', '')}
1463
+ </div>
1464
+ <div style="color: #333; font-size: 1.1em;">
1465
+ {entry['content']}
1466
+ </div>
1467
+ </div>
1468
+ """, unsafe_allow_html=True)
1469
+ else:
1470
+ # AI Response
1471
+ st.markdown(f"""
1472
+ <div style="
1473
+ background-color: #f8f9fa;
1474
+ padding: 15px;
1475
+ border-radius: 8px;
1476
+ margin: 8px 0;
1477
+ border-left: 4px solid #4caf50;
1478
+ ">
1479
+ <div style="color: #2e7d32;">
1480
+ 🤖 AI: {entry['content']}
1481
+ </div>
1482
+ </div>
1483
+ """, unsafe_allow_html=True)
1484
+
1485
+ elif entry['role'] == 'You':
1486
+ # User Entry
1487
+ status_icon = "✅" if entry.get('is_correct') else "✍️"
1488
+ bg_color = "#e8f5e9" if entry.get('is_correct') else "#fff"
1489
+ border_color = "#4caf50" if entry.get('is_correct') else "#1e88e5"
1490
+
1491
+ st.markdown(f"""
1492
+ <div style="
1493
+ background-color: {bg_color};
1494
+ padding: 15px;
1495
+ border-radius: 8px;
1496
+ margin: 8px 0;
1497
+ border-left: 4px solid {border_color};
1498
+ ">
1499
+ <div style="color: #333;">
1500
+ 👤 You: {status_icon} {entry['content']}
1501
+ </div>
1502
+ {f'<div style="font-size: 0.9em; color: #666; margin-top: 5px;">{entry.get("feedback", "")}</div>' if entry.get('feedback') else ''}
1503
+ </div>
1504
+ """, unsafe_allow_html=True)
1505
+
1506
+ def show_feedback_section():
1507
+ """Display writing feedback section"""
1508
+ if not st.session_state.feedback:
1509
+ return
1510
+
1511
+ st.markdown("""
1512
+ <div style="margin-bottom: 20px;">
1513
+ <div class="thai-eng">
1514
+ <div class="thai">📝 คำแนะนำจากครู</div>
1515
+ <div class="eng">Writing Feedback</div>
1516
+ </div>
1517
+ </div>
1518
+ """, unsafe_allow_html=True)
1519
+
1520
+ feedback_data = st.session_state.feedback
1521
+ if isinstance(feedback_data, dict) and feedback_data.get('has_errors'):
1522
+ # Show error feedback
1523
+ st.markdown(f"""
1524
+ <div style="
1525
+ background-color: #fff3e0;
1526
+ padding: 20px;
1527
+ border-radius: 10px;
1528
+ border-left: 4px solid #ff9800;
1529
+ margin: 10px 0;
1530
+ ">
1531
+ <p style="color: #e65100; margin-bottom: 15px;">
1532
+ {feedback_data['feedback']}
1533
+ </p>
1534
+ <div style="
1535
+ background-color: #fff;
1536
+ padding: 15px;
1537
+ border-radius: 8px;
1538
+ margin-top: 10px;
1539
+ ">
1540
+ <p style="color: #666; margin-bottom: 5px;">
1541
+ ประโยคที่ถูกต้อง:
1542
+ </p>
1543
+ <p style="
1544
+ color: #2e7d32;
1545
+ font-weight: 500;
1546
+ font-size: 1.1em;
1547
+ margin: 0;
1548
+ ">
1549
+ {feedback_data['corrected']}
1550
+ </p>
1551
+ </div>
1552
+ </div>
1553
+ """, unsafe_allow_html=True)
1554
+
1555
+ # Correction button
1556
+ if st.button(
1557
+ "✍️ แก้ไขประโยคให้ถูกต้อง",
1558
+ key="correct_button",
1559
+ help="คลิกเพื่อแก้ไขประโยคให้ถูกต้องตามคำแนะนำ"
1560
+ ):
1561
+ last_user_entry_idx = next(
1562
+ (i for i, entry in reversed(list(enumerate(st.session_state.story)))
1563
+ if entry['role'] == 'You'),
1564
+ None
1565
+ )
1566
+ if last_user_entry_idx is not None:
1567
+ apply_correction(last_user_entry_idx, feedback_data['corrected'])
1568
+ st.rerun()
1569
+ else:
1570
+ # Show success feedback
1571
+ st.markdown(f"""
1572
+ <div style="
1573
+ background-color: #e8f5e9;
1574
+ padding: 20px;
1575
+ border-radius: 10px;
1576
+ border-left: 4px solid #4caf50;
1577
+ margin: 10px 0;
1578
+ ">
1579
+ <p style="color: #2e7d32; margin: 0;">
1580
+ {feedback_data.get('feedback', '✨ เขียนได้ถูกต้องแล้วค่ะ!')}
1581
+ </p>
1582
+ </div>
1583
+ """, unsafe_allow_html=True)
1584
+
1585
+ def show_writing_tools():
1586
+ """Display writing tools section"""
1587
+ with st.expander("✨ เครื่องมือช่วยเขียน | Writing Tools"):
1588
+ col1, col2 = st.columns(2)
1589
+
1590
+ with col1:
1591
+ if st.button("🎯 ขอคำใบ้", use_container_width=True):
1592
+ with st.spinner("กำลังสร้างคำใบ้..."):
1593
+ prompt = get_creative_prompt()
1594
+ st.markdown(f"""
1595
+ <div style="
1596
+ background-color: #f3e5f5;
1597
+ padding: 15px;
1598
+ border-radius: 8px;
1599
+ margin-top: 10px;
1600
+ ">
1601
+ <div style="color: #6a1b9a;">💭 {prompt['thai']}</div>
1602
+ <div style="color: #666; font-style: italic;">
1603
+ 💭 {prompt['eng']}
1604
+ </div>
1605
+ </div>
1606
+ """, unsafe_allow_html=True)
1607
+
1608
+ with col2:
1609
+ if st.button("📚 คำศัพท์แนะนำ", use_container_width=True):
1610
+ with st.spinner("กำลังค้นหาคำศัพท์..."):
1611
+ vocab_suggestions = get_vocabulary_suggestions()
1612
+ st.markdown("#### 📚 คำศัพท์น่ารู้")
1613
+ for word in vocab_suggestions:
1614
+ st.markdown(f"""
1615
+ <div style="
1616
+ background-color: #f5f5f5;
1617
+ padding: 10px;
1618
+ border-radius: 5px;
1619
+ margin: 5px 0;
1620
+ ">
1621
+ • {word}
1622
+ </div>
1623
+ """, unsafe_allow_html=True)
1624
+
1625
+ def show_achievements():
1626
+ """Display achievements and stats"""
1627
+ with st.container():
1628
+ # Points and Streak
1629
+ st.markdown(f"""
1630
+ <div style="
1631
+ background-color: #e3f2fd;
1632
+ padding: 20px;
1633
+ border-radius: 10px;
1634
+ margin-bottom: 20px;
1635
+ text-align: center;
1636
+ ">
1637
+ <h3 style="color: #1e88e5; margin: 0;">
1638
+ 🌟 คะแนนรวม: {st.session_state.points['total']}
1639
+ </h3>
1640
+ <p style="margin: 5px 0;">
1641
+ 🔥 Streak ปัจจุบัน: {st.session_state.points['streak']} ประโยค
1642
+ </p>
1643
+ <p style="margin: 5px 0;">
1644
+ ⭐ Streak สูงสุด: {st.session_state.points['max_streak']} ประโยค
1645
+ </p>
1646
+ </div>
1647
+ """, unsafe_allow_html=True)
1648
+
1649
+ # Writing Stats
1650
+ st.markdown("""
1651
+ <div style="margin-bottom: 15px;">
1652
+ <h3>📊 สถิติการเขียน</h3>
1653
+ </div>
1654
+ """, unsafe_allow_html=True)
1655
+
1656
+ col1, col2 = st.columns(2)
1657
+ with col1:
1658
+ st.metric(
1659
+ "ประโยคที่เขียนทั้งหมด",
1660
+ st.session_state.stats['total_sentences'],
1661
+ help="จำนวนประโยคทั้งหมดที่คุณได้เขียน"
1662
+ )
1663
+ st.metric(
1664
+ "ถูกต้องตั้งแต่แรก",
1665
+ st.session_state.stats['correct_first_try'],
1666
+ help="จำนวนประโยคที่ถูกต้องโดยไม่ต้องแก้ไข"
1667
+ )
1668
+ with col2:
1669
+ st.metric(
1670
+ "ความแม่นยำ",
1671
+ f"{st.session_state.stats['accuracy_rate']:.1f}%",
1672
+ help="เปอร์เซ็นต์ของประโยคที่ถูกต้องตั้งแต่แรก"
1673
+ )
1674
+ st.metric(
1675
+ "คำศัพท์ที่ใช้",
1676
+ len(st.session_state.stats['vocabulary_used']),
1677
+ help="จำนวนคำศัพท์ที่ไม่ซ้ำกันที่คุณได้ใช้"
1678
+ )
1679
+
1680
+ # Show Achievements
1681
+ st.markdown("""
1682
+ <div style="margin: 25px 0 15px 0;">
1683
+ <h3>🏆 ความสำเร็จ</h3>
1684
+ </div>
1685
+ """, unsafe_allow_html=True)
1686
+
1687
+ if st.session_state.achievements:
1688
+ for achievement in st.session_state.achievements:
1689
+ st.success(achievement)
1690
+ else:
1691
+ st.info("ยังไม่มีความสำเร็จ - เขียนต่อไปเพื่อปลดล็อกรางวัล!")
1692
+
1693
+ # Show available achievements
1694
+ st.markdown("""
1695
+ <div style="
1696
+ margin-top: 15px;
1697
+ padding: 15px;
1698
+ background-color: #f5f5f5;
1699
+ border-radius: 8px;
1700
+ ">
1701
+ <p style="color: #666; margin-bottom: 10px;">
1702
+ รางวัลที่รอคุณอยู่:
1703
+ </p>
1704
+ <ul style="list-style-type: none; padding-left: 0;">
1705
+ <li>🌟 นักเขียนไร้ที่ติ: เขียนถูกต้อง 5 ประโยคติดต่อกัน</li>
1706
+ <li>📚 ราชาคำศัพท์: ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ</li>
1707
+ <li>📖 นักแต่งนิทาน: เขียนเรื่องยาว 10 ประโยค</li>
1708
+ <li>👑 ราชาความแม่นยำ: มีอัตราความถูกต้อง 80% ขึ้นไป</li>
1709
+ </ul>
1710
+ </div>
1711
+ """, unsafe_allow_html=True)
1712
+
1713
+ def show_save_options():
1714
+ """Display save and export options"""
1715
+ st.markdown("### 💾 บันทึกเรื่องราว")
1716
+
1717
+ if not st.session_state.story:
1718
+ st.info("เริ่มเขียนเรื่องราวก่อนเพื่อบันทึกผลงานของคุณ")
1719
+ return
1720
+
1721
+ col1, col2 = st.columns(2)
1722
+ with col1:
1723
+ # PDF Export
1724
+ if st.button("📑 บันทึกเป็น PDF", use_container_width=True):
1725
+ try:
1726
+ pdf = create_story_pdf()
1727
+ st.download_button(
1728
+ "📥 ดาวน์โหลด PDF",
1729
+ data=pdf,
1730
+ file_name=f"story_{datetime.now().strftime('%Y%m%d')}.pdf",
1731
+ mime="application/pdf"
1732
+ )
1733
+ except Exception as e:
1734
+ logging.error(f"Error creating PDF: {str(e)}")
1735
+ st.error("เกิดข้อผิดพลาดในการสร้างไฟล์ PDF กรุณาลองใหม่อีกครั้ง")
1736
+
1737
+ with col2:
1738
+ # JSON Save
1739
+ if st.button("💾 บันทึกความก้าวหน้า", use_container_width=True):
1740
+ try:
1741
+ story_data = {
1742
+ 'timestamp': datetime.now().isoformat(),
1743
+ 'level': st.session_state.level,
1744
+ 'story': st.session_state.story,
1745
+ 'achievements': st.session_state.achievements,
1746
+ 'stats': convert_sets_to_lists(st.session_state.stats),
1747
+ 'points': st.session_state.points
1748
+ }
1749
+
1750
+ st.download_button(
1751
+ "📥 ดาวน์โหลดไฟล์บันทึก",
1752
+ data=json.dumps(story_data, ensure_ascii=False, indent=2),
1753
+ file_name=f"story_progress_{datetime.now().strftime('%Y%m%d')}.json",
1754
+ mime="application/json"
1755
+ )
1756
+ except Exception as e:
1757
+ logging.error(f"Error saving progress: {str(e)}")
1758
+ st.error("เกิดข้อผิดพลาดในการบันทึกความก้าวหน้า กรุณาลองใหม่อีกครั้ง")
1759
+
1760
+ def show_sidebar():
1761
+ """Display sidebar content"""
1762
+ st.sidebar.markdown("### ⚙️ การตั้งค่า")
1763
+
1764
+ # Progress Upload
1765
+ st.sidebar.markdown("#### 📂 โหลดความก้าวหน้า")
1766
+ uploaded_file = st.sidebar.file_uploader(
1767
+ "เลือกไฟล์ .json",
1768
+ type=['json'],
1769
+ help="เลือกไฟล์ความก้าวหน้าที่บันทึกไว้"
1770
+ )
1771
+ if uploaded_file:
1772
+ if st.sidebar.button("โหลดความก้าวหน้า", use_container_width=True):
1773
+ try:
1774
+ data = json.loads(uploaded_file.getvalue())
1775
+ load_progress(data)
1776
+ st.sidebar.success("โหลดความก้าวหน้าเรียบร้อย!")
1777
+ st.rerun()
1778
+ except Exception as e:
1779
+ logging.error(f"Error loading progress: {str(e)}")
1780
+ st.sidebar.error("เกิดข้อผิดพลาดในการโหลดไฟล์")
1781
+
1782
+ # Level Selection
1783
+ st.sidebar.markdown("#### 🎯 ระดับการเรียน")
1784
+ level = show_level_selection()
1785
+ st.session_state.level = level
1786
+
1787
+ # Theme Change Option
1788
+ if st.session_state.current_theme:
1789
+ st.sidebar.markdown("#### 🎨 ธีมเรื่องราว")
1790
+ if st.sidebar.button("🔄 เปลี่ยนธีม", use_container_width=True):
1791
+ st.session_state.current_theme = None
1792
+ st.session_state.theme_story_starter = None
1793
+ st.rerun()
1794
+
1795
+ # Reset Option
1796
+ st.sidebar.markdown("#### 🔄 รีเซ็ตเรื่องราว")
1797
+ if st.sidebar.button("เริ่มเรื่องใหม่", use_container_width=True):
1798
+ if st.session_state.story:
1799
+ if st.sidebar.checkbox("ยืนยันการเริ่มใหม่"):
1800
+ st.session_state.should_reset = True
1801
+ st.rerun()
1802
+ else:
1803
+ st.sidebar.info("ยังไม่มีเรื่องราวที่จะรีเซ็ต")
1804
+
1805
+ def show_story_input():
1806
+ """Display story input section"""
1807
+ st.markdown("""
1808
+ <div class="thai-eng">
1809
+ <div class="thai">✏️ ถึงตาคุณแล้ว</div>
1810
+ <div class="eng">Your Turn</div>
1811
+ </div>
1812
+ """, unsafe_allow_html=True)
1813
+
1814
+ # Initialize clear_input flag if not exists
1815
+ if 'clear_input' not in st.session_state:
1816
+ st.session_state.clear_input = False
1817
+
1818
+ # Default value for text input
1819
+ default_value = "" if st.session_state.clear_input else st.session_state.get('text_input', "")
1820
+
1821
+ # If clear_input flag is True, reset it
1822
+ if st.session_state.clear_input:
1823
+ st.session_state.clear_input = False
1824
+
1825
+ # Input area
1826
+ text_input = st.text_area(
1827
+ "เขียนต่อจากเรื่องราว | Continue the story:",
1828
+ value=default_value,
1829
+ height=100,
1830
+ key="story_input_area",
1831
+ help="พิมพ์ประโยคภาษาอังกฤษเพื่อต่อเรื่อง",
1832
+ label_visibility="collapsed"
1833
+ )
1834
+
1835
+ # Save current input to session state
1836
+ st.session_state.text_input = text_input
1837
+
1838
+ # Submit button with character count
1839
+ col1, col2 = st.columns([3, 1])
1840
+ with col1:
1841
+ if st.button("📝 ส่งคำตอบ | Submit", use_container_width=True):
1842
+ if not text_input.strip():
1843
+ st.warning("กรุณาเขียนข้อความก่อนส่ง")
1844
+ return
1845
+
1846
+ try:
1847
+ with st.spinner("กำลังวิเคราะห์ประโยค..."):
1848
+ handle_story_submission(text_input.strip())
1849
+ except Exception as e:
1850
+ logging.error(f"Error submitting story: {str(e)}")
1851
+ st.error("เกิดข้อผิดพลาดในการส่งคำตอบ กรุณาลองใหม่อีกครั้ง")
1852
+
1853
+ with col2:
1854
+ char_count = len(text_input)
1855
+ st.markdown(f"""
1856
+ <div style="text-align: right; color: {'red' if char_count > 200 else '#666'};">
1857
+ {char_count}/200 ตัวอักษร
1858
+ </div>
1859
+ """, unsafe_allow_html=True)
1860
+
1861
+ def handle_story_submission(text: str):
1862
+ """Handle story submission and processing"""
1863
+ if not st.session_state.story:
1864
+ st.error("กรุณาเลือกธีม���รื่องราวก่อนเริ่มเขียน")
1865
+ return
1866
+
1867
+ try:
1868
+ # Get feedback
1869
+ feedback_data = provide_feedback(text, st.session_state.level)
1870
+ st.session_state.feedback = feedback_data
1871
+ is_correct = not feedback_data.get('has_errors', False)
1872
+
1873
+ # Add user's sentence
1874
+ st.session_state.story.append({
1875
+ "role": "You",
1876
+ "content": text,
1877
+ "is_corrected": False,
1878
+ "is_correct": is_correct,
1879
+ "timestamp": datetime.now().isoformat()
1880
+ })
1881
+
1882
+ # Update vocabulary
1883
+ words = set(text.lower().split())
1884
+ st.session_state.stats['vocabulary_used'].update(words)
1885
+
1886
+ # Update points and achievements
1887
+ update_points(is_correct)
1888
+ update_achievements()
1889
+
1890
+ # Generate AI continuation
1891
+ try:
1892
+ logging.info("Attempting to generate AI continuation...")
1893
+ # ใช้ข้อความที่ถูกต้องสำหรับการต่อเรื่อง
1894
+ text_for_continuation = feedback_data['corrected'] if feedback_data.get('has_errors') else text
1895
+
1896
+ ai_response = generate_story_continuation(text_for_continuation, st.session_state.level)
1897
+
1898
+ # Log the AI response
1899
+ logging.info(f"AI Response generated: {ai_response}")
1900
+
1901
+ if ai_response and ai_response.strip():
1902
+ st.session_state.story.append({
1903
+ "role": "AI",
1904
+ "content": ai_response,
1905
+ "timestamp": datetime.now().isoformat()
1906
+ })
1907
+ logging.info("AI response added to story successfully")
1908
+ else:
1909
+ logging.error("AI generated empty response")
1910
+ # กรณีที่ AI ไม่สร้างประโยค ให้สร้างประโยคง่ายๆ ตาม theme
1911
+ fallback_response = generate_fallback_response(st.session_state.current_theme, st.session_state.level)
1912
+ st.session_state.story.append({
1913
+ "role": "AI",
1914
+ "content": fallback_response,
1915
+ "timestamp": datetime.now().isoformat()
1916
+ })
1917
+ except Exception as e:
1918
+ logging.error(f"Error generating AI continuation: {str(e)}")
1919
+ # สร้าง fallback response เมื่อเกิดข้อผิดพลาด
1920
+ fallback_response = generate_fallback_response(st.session_state.current_theme, st.session_state.level)
1921
+ st.session_state.story.append({
1922
+ "role": "AI",
1923
+ "content": fallback_response,
1924
+ "timestamp": datetime.now().isoformat()
1925
+ })
1926
+
1927
+ # Update session stats
1928
+ update_session_stats()
1929
+
1930
+ # Set flag to clear input on next rerun
1931
+ st.session_state.clear_input = True
1932
+
1933
+ # Rerun to update UI
1934
+ st.rerun()
1935
+
1936
+ except Exception as e:
1937
+ logging.error(f"Error in story submission: {str(e)}")
1938
+ raise
1939
+
1940
+ def generate_fallback_response(theme_id: str, level: str) -> str:
1941
+ """Generate a simple fallback response when AI continuation fails"""
1942
+ try:
1943
+ theme = story_themes.get(theme_id, {})
1944
+ if theme:
1945
+ # ใช้คำศัพท์จาก theme และ level ที่เลือก
1946
+ vocab = theme.get('vocabulary', {}).get(level, [])
1947
+ if vocab:
1948
+ word = random.choice(vocab)
1949
+
1950
+ # สร้างประโยคตาม level
1951
+ if level == 'Beginner':
1952
+ return f"The story continues with {word}..."
1953
+ elif level == 'Intermediate':
1954
+ return f"Something interesting happened with the {word}."
1955
+ else: # Advanced
1956
+ return f"The mystery deepens as we discover more about the {word}."
1957
+
1958
+ # Default fallback if no theme-specific response can be generated
1959
+ return "The story continues..."
1960
+
1961
+ except Exception as e:
1962
+ logging.error(f"Error generating fallback response: {str(e)}")
1963
+ return "What happens next?"
1964
+
1965
+ def show_main_interface():
1966
+ """Display main story interface"""
1967
+ col1, col2 = st.columns([3, 1])
1968
+
1969
+ with col1:
1970
+ # Story display
1971
+ st.markdown("""
1972
+ <div class="thai-eng">
1973
+ <div class="thai">📖 เรื่องราวของคุณ</div>
1974
+ <div class="eng">Your Story</div>
1975
+ </div>
1976
+ """, unsafe_allow_html=True)
1977
+
1978
+ show_story()
1979
+
1980
+ if st.session_state.story:
1981
+ show_story_input()
1982
+
1983
+ with col2:
1984
+ # Feedback section
1985
+ show_feedback_section()
1986
+
1987
+ # Writing tools
1988
+ show_writing_tools()
1989
+
1990
+ # Achievements
1991
+ with st.expander("🏆 ความสำเร็จ | Achievements"):
1992
+ show_achievements()
1993
+
1994
+ # Save options
1995
+ show_save_options()
1996
+
1997
+ # === 6. MAIN APPLICATION LOGIC ===
1998
+ def main():
1999
+ """Main application entry point"""
2000
+ try:
2001
+ # Initialize states
2002
+ init_session_state()
2003
+ init_theme_state()
2004
+
2005
+ # Add watermark
2006
+ st.markdown("""
2007
+ <div style='position: fixed; bottom: 10px; right: 10px; z-index: 1000;
2008
+ opacity: 0.7; font-size: 0.8em; color: #666;'>
2009
+ Powered by JoyStory AI
2010
+ </div>
2011
+ """, unsafe_allow_html=True)
2012
+
2013
+ # Show header
2014
+ st.markdown("# 📖 JoyStory")
2015
+ show_welcome_section()
2016
+ show_parent_guide()
2017
+
2018
+ # Sidebar
2019
+ with st.sidebar:
2020
+ show_sidebar()
2021
+
2022
+ # Session Status Check
2023
+ check_session_status()
2024
+
2025
+ # Main content area
2026
+ main_container = st.container()
2027
+ with main_container:
2028
+ if not st.session_state.current_theme:
2029
+ show_theme_selection()
2030
+ else:
2031
+ show_main_interface()
2032
+
2033
+ # Handle reset if needed
2034
+ if st.session_state.should_reset:
2035
+ reset_story()
2036
+
2037
+ # Auto-save progress periodically
2038
+ if st.session_state.story:
2039
+ auto_save_progress()
2040
+
2041
+ except Exception as e:
2042
+ handle_application_error(e)
2043
+
2044
+ def check_session_status():
2045
+ """Check and maintain session status"""
2046
+ try:
2047
+ # Update session duration
2048
+ if 'session_start' not in st.session_state:
2049
+ st.session_state.session_start = datetime.now()
2050
+
2051
+ # Check for session timeout (2 hours)
2052
+ session_duration = (datetime.now() - st.session_state.session_start).total_seconds()
2053
+ if session_duration > 7200: # 2 hours
2054
+ st.warning("เซสชันหมดอายุ กรุณาบันทึกความก้าวหน้าและรีเฟรชหน้าเว็บ")
2055
+
2056
+ # Check for inactivity (30 minutes)
2057
+ last_interaction = datetime.fromisoformat(st.session_state.last_interaction)
2058
+ inactivity_duration = (datetime.now() - last_interaction).total_seconds()
2059
+ if inactivity_duration > 1800: # 30 minutes
2060
+ st.info("ไม่มีกิจกรรมเป็นเวลานาน กรุณาบันทึกความก้าวหน้าเพื่อความปลอดภัย")
2061
+
2062
+ # Update stats if story exists
2063
+ if st.session_state.story:
2064
+ update_session_stats()
2065
+
2066
+ except Exception as e:
2067
+ logging.error(f"Error checking session status: {str(e)}")
2068
+
2069
+ def auto_save_progress():
2070
+ """Automatically save progress to session state"""
2071
+ try:
2072
+ current_progress = {
2073
+ 'timestamp': datetime.now().isoformat(),
2074
+ 'story': st.session_state.story,
2075
+ 'stats': st.session_state.stats,
2076
+ 'points': st.session_state.points,
2077
+ 'achievements': st.session_state.achievements
2078
+ }
2079
+
2080
+ # Save to session state
2081
+ if 'auto_save' not in st.session_state:
2082
+ st.session_state.auto_save = {}
2083
+
2084
+ st.session_state.auto_save = current_progress
2085
+
2086
+ # Add timestamp for last auto-save
2087
+ st.session_state.last_auto_save = datetime.now().isoformat()
2088
+
2089
+ except Exception as e:
2090
+ logging.error(f"Error in auto-save: {str(e)}")
2091
+
2092
+ def handle_application_error(error: Exception):
2093
+ """Handle application-wide errors"""
2094
+ logging.error(f"Application error: {str(error)}")
2095
+
2096
+ error_message = """
2097
+ <div style="
2098
+ background-color: #ffebee;
2099
+ padding: 20px;
2100
+ border-radius: 10px;
2101
+ border-left: 4px solid #c62828;
2102
+ margin: 20px 0;
2103
+ ">
2104
+ <h3 style="color: #c62828; margin: 0 0 10px 0;">
2105
+ ⚠️ เกิดข้อผิดพลาดในระบบ
2106
+ </h3>
2107
+ <p style="color: #333; margin: 0;">
2108
+ กรุณาลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบ
2109
+ </p>
2110
+ </div>
2111
+ """
2112
+
2113
+ st.markdown(error_message, unsafe_allow_html=True)
2114
+
2115
+ # Show technical details in expander
2116
+ with st.expander("รายละเอียดข้อผิดพลาด (สำหรับผู้ดูแลระบบ)"):
2117
+ st.code(f"""
2118
+ Error Type: {type(error).__name__}
2119
+ Error Message: {str(error)}
2120
+ Timestamp: {datetime.now().isoformat()}
2121
+ """)
2122
+
2123
+ def show_debug_info():
2124
+ """Show debug information (development only)"""
2125
+ if st.session_state.get('debug_mode'):
2126
+ with st.expander("🔧 Debug Information"):
2127
+ st.json({
2128
+ 'session_state': {
2129
+ key: str(value) if isinstance(value, (set, datetime)) else value
2130
+ for key, value in st.session_state.items()
2131
+ if key not in ['client', '_client']
2132
+ }
2133
+ })
2134
+
2135
+ # Add CSS for loading animation
2136
+ st.markdown("""
2137
+ <style>
2138
+ @keyframes pulse {
2139
+ 0% { opacity: 0.6; }
2140
+ 50% { opacity: 1; }
2141
+ 100% { opacity: 0.6; }
2142
+ }
2143
+
2144
+ .loading-animation {
2145
+ animation: pulse 1.5s infinite;
2146
+ background-color: #f0f2f5;
2147
+ border-radius: 8px;
2148
+ padding: 20px;
2149
+ text-align: center;
2150
+ margin: 20px 0;
2151
+ }
2152
+
2153
+ /* Custom Scrollbar */
2154
+ ::-webkit-scrollbar {
2155
+ width: 8px;
2156
+ height: 8px;
2157
+ }
2158
+
2159
+ ::-webkit-scrollbar-track {
2160
+ background: #f1f1f1;
2161
+ border-radius: 4px;
2162
+ }
2163
+
2164
+ ::-webkit-scrollbar-thumb {
2165
+ background: #888;
2166
+ border-radius: 4px;
2167
+ }
2168
+
2169
+ ::-webkit-scrollbar-thumb:hover {
2170
+ background: #666;
2171
+ }
2172
+
2173
+ /* Toast Notifications */
2174
+ .toast-notification {
2175
+ position: fixed;
2176
+ bottom: 20px;
2177
+ right: 20px;
2178
+ padding: 15px 25px;
2179
+ background-color: #333;
2180
+ color: white;
2181
+ border-radius: 8px;
2182
+ z-index: 1000;
2183
+ animation: slideIn 0.3s ease-out;
2184
+ }
2185
+
2186
+ @keyframes slideIn {
2187
+ from { transform: translateX(100%); }
2188
+ to { transform: translateX(0); }
2189
+ }
2190
+
2191
+ /* Progress Indicator */
2192
+ .progress-bar {
2193
+ width: 100%;
2194
+ height: 4px;
2195
+ background-color: #e0e0e0;
2196
+ border-radius: 2px;
2197
+ overflow: hidden;
2198
+ }
2199
+
2200
+ .progress-bar-fill {
2201
+ height: 100%;
2202
+ background-color: #1e88e5;
2203
+ transition: width 0.3s ease;
2204
+ }
2205
+ </style>
2206
+ """, unsafe_allow_html=True)
2207
+
2208
+ if __name__ == "__main__":
2209
+ try:
2210
+ main()
2211
+ except Exception as e:
2212
+ handle_application_error(e)