File size: 17,121 Bytes
e046fa6
dfb26ce
7dd1646
 
 
dfb26ce
5dde3ba
 
 
 
 
 
 
 
 
 
 
49c245f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5dde3ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfb26ce
5dde3ba
e046fa6
49c245f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7dd1646
 
 
 
 
 
 
 
695191e
 
 
 
7dd1646
695191e
7dd1646
 
 
 
 
 
 
 
49c245f
7dd1646
cc3c1dd
 
7dd1646
bd91ee2
7dd1646
49c245f
 
 
 
 
 
7dd1646
49c245f
cc3c1dd
7dd1646
49c245f
7dd1646
 
49c245f
 
 
cc3c1dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7dd1646
49c245f
 
c38ea88
7dd1646
 
 
 
c38ea88
 
 
 
 
 
 
 
7dd1646
c38ea88
7dd1646
 
49c245f
 
 
 
 
 
c38ea88
49c245f
 
c38ea88
49c245f
 
 
 
 
7dd1646
 
49c245f
c38ea88
 
49c245f
7dd1646
c38ea88
 
7dd1646
 
bd91ee2
7dd1646
c38ea88
 
 
 
 
 
 
 
 
bd91ee2
c38ea88
 
 
 
 
bd91ee2
c38ea88
7dd1646
c38ea88
bd91ee2
7dd1646
 
 
 
c38ea88
bd91ee2
c38ea88
bd91ee2
c38ea88
 
 
 
 
 
 
bd91ee2
 
 
 
 
 
 
c38ea88
 
bd91ee2
 
 
c38ea88
 
 
 
 
 
 
 
 
 
 
bd91ee2
c38ea88
 
 
 
 
7dd1646
 
 
 
dfb26ce
 
7dd1646
 
 
 
 
 
 
 
 
 
 
 
95bfa53
 
 
695191e
95bfa53
 
 
 
 
 
 
 
 
7dd1646
49c245f
 
dfb26ce
7dd1646
 
49c245f
 
 
 
 
5dde3ba
7dd1646
 
49c245f
 
7dd1646
5dde3ba
 
 
 
 
 
7dd1646
 
dfb26ce
 
 
 
5dde3ba
 
 
 
 
 
7dd1646
5dde3ba
7dd1646
 
5dde3ba
7dd1646
 
695191e
 
 
 
7dd1646
dfb26ce
5dde3ba
 
 
 
 
 
 
 
7dd1646
5dde3ba
7dd1646
 
dfb26ce
695191e
7dd1646
 
95bfa53
7dd1646
5dde3ba
dfb26ce
 
5dde3ba
 
 
 
 
 
 
 
 
 
 
49c245f
 
5dde3ba
 
49c245f
 
695191e
5dde3ba
dfb26ce
49c245f
dfb26ce
49c245f
7dd1646
5dde3ba
49c245f
5dde3ba
49c245f
5dde3ba
 
49c245f
5dde3ba
7dd1646
 
5dde3ba
 
 
 
 
 
 
dfb26ce
 
 
 
5dde3ba
7dd1646
 
 
5dde3ba
7dd1646
 
 
 
 
 
 
 
 
5dde3ba
7dd1646
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
import streamlit as st
import json
import datetime
from openai import OpenAI
from typing import Dict, List, Set

# Set up Streamlit page configuration
st.set_page_config(
    page_title="JoyStory - Interactive Story Adventure",
    page_icon="📖",
    layout="wide",
    initial_sidebar_state="collapsed",
)

# Initialize OpenAI client
client = OpenAI()

# Custom CSS for Thai-English bilingual display
st.markdown("""
    <style>
    .thai-eng {
        font-size: 1.1em;
        padding: 10px;
        background-color: #f8f9fa;
        border-radius: 8px;
        margin: 10px 0;
    }
    .thai {
        color: #1e88e5;
        font-family: 'Sarabun', sans-serif;
    }
    .eng {
        color: #333;
    }
    .highlight {
        background-color: #e3f2fd;
        padding: 2px 5px;
        border-radius: 4px;
    }
    </style>
""", unsafe_allow_html=True)

# Initialize session state variables
def init_session_state():
    if 'story' not in st.session_state:
        st.session_state.story = []
    if 'feedback' not in st.session_state:
        st.session_state.feedback = None
    if 'level' not in st.session_state:
        st.session_state.level = 'Beginner'
    if 'unique_words' not in st.session_state:
        st.session_state.unique_words = set()
    if 'total_words' not in st.session_state:
        st.session_state.total_words = 0
    if 'badges' not in st.session_state:
        st.session_state.badges = []
    if 'should_reset' not in st.session_state:
        st.session_state.should_reset = False

init_session_state()

def show_welcome_section():
    st.markdown("""
        <div class="thai-eng">
            <div class="thai">
            🌟 ยินดีต้อนรับสู่ JoyStory - แอพฝึกเขียนภาษาอังกฤษแสนสนุก!
            <br>
            เรียนรู้ภาษาอังกฤษผ่านการเขียนเรื่องราวด้วยตัวเอง พร้อมผู้ช่วย AI ที่จะช่วยแนะนำและให้กำลังใจ
            </div>
            <div class="eng">
            Welcome to JoyStory - Fun English Writing Adventure!
            </div>
        </div>
    """, unsafe_allow_html=True)

    st.markdown("""
        <div class="thai-eng">
            <div class="thai">
            📝 วิธีการใช้งาน:
            <br>• เลือกระดับภาษาของน้องๆ ที่เมนูด้านข้าง
            <br>• เริ่มเขียนเรื่องราวที่น้องๆ อยากเล่า
            <br>• AI จะช่วยต่อเรื่องและให้คำแนะนำ
            <br>• สะสมคะแนนและรางวัลจากการเขียน
            </div>
        </div>
    """, unsafe_allow_html=True)

def generate_story_continuation(user_input: str, level: str) -> str:
    """Generate AI story continuation using ChatGPT."""
    try:
        story_context = '\n'.join([entry['content'] for entry in st.session_state.story])
        
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": f"""You are a creative storytelling assistant for {level} English learners. 
                Keep the language simple and engaging. Respond with only 1-2 short sentences to continue the story.
                Avoid long descriptions and let the user contribute to the story development."""},
                {"role": "user", "content": f"Story so far:\n{story_context}\nUser's addition:\n{user_input}\nContinue the story with 1-2 short sentences:"}
            ],
            max_tokens=50,
            temperature=0.7
        )
        return response.choices[0].message.content
    except Exception as e:
        st.error(f"Error generating story continuation: {str(e)}")
        return "I'm having trouble continuing the story. Please try again."

def get_vocabulary_suggestions() -> List[str]:
    """Get contextual vocabulary suggestions with Thai translations."""
    try:
        recent_story = '\n'.join([entry['content'] for entry in st.session_state.story[-3:]] if st.session_state.story else "Story just started")
        
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": f"""You are a Thai-English bilingual teacher.
                Suggest 5 English words with their Thai translations and examples.
                Format each suggestion as:
                word (type) - คำแปล | example sentence
                Make sure words match {st.session_state.level} level."""},
                {"role": "user", "content": f"Story context:\n{recent_story}\n\nSuggest 5 relevant words with Thai translations:"}
            ],
            max_tokens=200,
            temperature=0.8
        )
        return response.choices[0].message.content.split('\n')
    except Exception as e:
        st.error(f"Error getting vocabulary suggestions: {str(e)}")
        return ["happy (adj) - มีความสุข | I am happy today",
                "run (verb) - วิ่ง | The dog runs fast",
                "tree (noun) - ต้นไม้ | A tall tree"]

# In the main UI section, update how vocabulary suggestions are displayed:
    if st.button("Get Vocabulary Ideas"):
        vocab_suggestions = get_vocabulary_suggestions()
        st.markdown("#### 📚 Suggested Words")
        for word in vocab_suggestions:
            st.markdown(f"• *{word}*")

# And update how feedback is displayed to be more concise:
    if st.session_state.feedback:
        st.markdown("""
            <div style='background-color: #f0f2f6; padding: 8px; border-radius: 4px; margin-bottom: 10px;'>
                📝 <i>{}</i>
            </div>
            """.format(st.session_state.feedback), unsafe_allow_html=True)

# Update the creative prompt function
def get_creative_prompt() -> Dict[str, str]:
    """Generate a short, simple bilingual creative prompt."""
    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": """Create very short story prompts in both English and Thai.
                Keep it simple and under 6 words each.
                Example formats:
                - "What did the cat find?"
                - "Where did they go next?"
                - "How does the story end?"
                """},
                {"role": "user", "content": "Generate a simple, short story prompt:"}
            ],
            max_tokens=50,
            temperature=0.7
        )
        prompt_eng = response.choices[0].message.content
        
        # Get Thai translation
        response_thai = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": "Translate to short Thai prompt, keep it simple and natural:"},
                {"role": "user", "content": prompt_eng}
            ],
            max_tokens=50,
            temperature=0.7
        )
        prompt_thai = response_thai.choices[0].message.content
        
        return {"eng": prompt_eng, "thai": prompt_thai}
    except Exception as e:
        st.error(f"Error generating creative prompt: {str(e)}")
        return {
            "eng": "What happens next?",
            "thai": "แล้วอะไรจะเกิดขึ้นต่อ?"
        }

def provide_feedback(text: str, level: str) -> str:
    """Provide educational feedback in Thai with English examples."""
    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": f"""You are a Thai English teacher helping {level} students.
                Provide feedback in Thai language that:
                1. Points out ONE main grammar or vocabulary point
                2. Explains the correct usage in simple Thai
                3. Shows example sentences
                4. Gives encouragement

                Format example:
                "🎯 คำว่า 'go' เมื่อพูดถึงเหตุการณ์ในอดีต ต้องเปลี่ยนเป็น 'went' นะคะ
                
                ตัวอย่าง:
                - ถูก: Yesterday, I went to school.
                - ผิด: Yesterday, I go to school.

                เก่งมากๆ เลยค่ะ ที่พยายามเขียนเป็นภาษาอังกฤษ! ✨"
                """},
                {"role": "user", "content": f"Review this sentence and provide feedback in Thai: {text}"}
            ],
            max_tokens=200,
            temperature=0.7
        )
        return response.choices[0].message.content
    except Exception as e:
        st.error(f"Error generating feedback: {str(e)}")
        return "🌟 พยายามได้ดีมากค่ะ/ครับ ลองเขียนต่อไปนะ!"

# Update the feedback display in the UI
    if st.session_state.feedback:
        st.markdown("""
            <div class="thai-eng">
                <div class="thai">📝 คำแนะนำจากคุณครู</div>
                <div class="eng">Teacher's Feedback</div>
            </div>
        """, unsafe_allow_html=True)
        
        feedback_container = st.container()
        with feedback_container:
            st.markdown(f"""
                <div style='background-color: #f0f2f6; 
                           padding: 10px; 
                           border-radius: 8px; 
                           border-left: 4px solid #4CAF50;
                           margin: 5px 0;
                           font-family: "Sarabun", sans-serif;'>
                    {st.session_state.feedback}
                </div>
            """, unsafe_allow_html=True)
            
# Example feedback output will look like:
"""
🎯 คำว่า 'tree' เมื่อพูดถึงหลายต้น ต้องเติม s เป็น 'trees' นะคะ

ตัวอย่าง:
- ถูก: There are many trees in the forest.
- ผิด: There are many tree in the forest.

เขียนได้ดีมากเลยค่ะ ประโยคสื่อความหมายได้ชัดเจน! ✨
"""

# Example creative prompt will be simpler like:
"""
💭 ต่อไปตัวละครจะไปไหน?
Where will they go next?
"""

def update_achievements(text: str):
    """Update user achievements based on their writing."""
    words = set(text.lower().split())
    st.session_state.unique_words.update(words)
    st.session_state.total_words += len(words)
    
    achievements = {
        '50 Words': len(st.session_state.unique_words) >= 50,
        '100 Words': len(st.session_state.unique_words) >= 100,
        'Story Master': len(st.session_state.story) >= 10,
    }
    
    for badge, condition in achievements.items():
        if condition and badge not in st.session_state.badges:
            st.session_state.badges.append(badge)
            st.success(f"🏆 Achievement Unlocked: {badge}!")

def reset_story():
    """Reset the story and related state variables."""
    st.session_state.story = []
    st.session_state.feedback = None
    st.session_state.unique_words = set()
    st.session_state.total_words = 0
    st.session_state.badges = []
    st.session_state.should_reset = False

# Handle story reset if needed
if st.session_state.should_reset:
    reset_story()

# Main UI Layout
st.markdown("# 📖 JoyStory")
show_welcome_section()

# Sidebar for settings
with st.sidebar:
    st.markdown("""
        <div class="thai">
        🎯 เลือกระดับของน้องๆ
        </div>
    """, unsafe_allow_html=True)
    
    level = st.radio(
        "Select your English level:",
        ('Beginner', 'Intermediate', 'Advanced'),
        index=['Beginner', 'Intermediate', 'Advanced'].index(st.session_state.level)
    )
    
    st.session_state.level = level
    
    if st.button("Start New Story"):
        st.session_state.should_reset = True
        st.rerun()

# Main content area
col1, col2 = st.columns([3, 1])

with col1:
    # Story Display Box
    st.markdown("""
        <div class="thai-eng">
            <div class="thai">📖 เรื่องราวของคุณ</div>
            <div class="eng">Your Story</div>
        </div>
    """, unsafe_allow_html=True)
    
    story_display = st.container()
    with story_display:
        if not st.session_state.story:
            st.info("เริ่มต้นผจญภัยด้วยการเขียนประโยคแรกกันเลย! | Start your adventure by writing the first sentence!")
        else:
            for entry in st.session_state.story:
                if entry['role'] == 'You':
                    st.write("👤 You:", entry['content'])
                elif entry['role'] == 'AI':
                    st.write("🤖 AI:", entry['content'])
    
    # User Input Box
    st.markdown("""
        <div class="thai-eng">
            <div class="thai">✏️ ถึงตาคุณแล้ว</div>
            <div class="eng">Your Turn</div>
        </div>
    """, unsafe_allow_html=True)
    
    user_input = st.text_area("เขียนต่อจากเรื่องราว | Continue the story:", height=100)
    
    if st.button("ส่งคำตอบ | Submit"):
        if user_input.strip():
            st.session_state.story.append({"role": "You", "content": user_input})
            update_achievements(user_input)
            st.session_state.feedback = provide_feedback(user_input, st.session_state.level)
            ai_response = generate_story_continuation(user_input, st.session_state.level)
            st.session_state.story.append({"role": "AI", "content": ai_response})
            st.rerun()
        else:
            st.warning("กรุณาเขียนเนื้อเรื่องก่อนกดส่ง | Please write something to continue the story.")

with col2:
    # Feedback Display
    if st.session_state.feedback:
        st.markdown("""
            <div class="thai-eng">
                <div class="thai">📝 คำแนะนำ</div>
                <div class="eng">Writing Feedback</div>
            </div>
        """, unsafe_allow_html=True)
        st.markdown(f"*{st.session_state.feedback}*")
    
    # Help and Suggestions Box
    st.markdown("""
        <div class="thai-eng">
            <div class="thai">✨ เครื่องมือช่วยเขียน</div>
            <div class="eng">Writing Tools</div>
        </div>
    """, unsafe_allow_html=True)
    
    if st.button("ดูคำศัพท์แนะนำ | Get Vocabulary Ideas"):
        vocab_suggestions = get_vocabulary_suggestions()
        st.markdown("#### 📚 คำศัพท์น่ารู้ | Useful Words")
        for word in vocab_suggestions:
            st.markdown(f"• {word}")
    
    if st.button("ขอคำใบ้ | Get Creative Prompt"):
        prompt = get_creative_prompt()
        st.markdown(f"""
            <div class="thai-eng">
                <div class="thai">💭 {prompt['thai']}</div>
                <div class="eng">💭 {prompt['eng']}</div>
            </div>
        """, unsafe_allow_html=True)
    
    # Achievements Box
    st.markdown("""
        <div class="thai-eng">
            <div class="thai">🏆 ความสำเร็จ</div>
            <div class="eng">Achievements</div>
        </div>
    """, unsafe_allow_html=True)
    
    if st.session_state.badges:
        for badge in st.session_state.badges:
            st.success(f"🏆 {badge}")
    else:
        st.write("เขียนต่อไปเพื่อรับรางวัล | Keep writing to earn badges!")
    
    # Save Story Button
    if st.session_state.story:
        if st.button("บันทึกเรื่องราว | Save Story"):
            story_data = {
                'level': st.session_state.level,
                'date': datetime.datetime.now().isoformat(),
                'story': st.session_state.story,
                'achievements': st.session_state.badges,
                'total_words': st.session_state.total_words,
                'unique_words': list(st.session_state.unique_words)
            }
            st.download_button(
                label="ดาวน์โหลดเรื่องราว | Download Story",
                data=json.dumps(story_data, indent=2),
                file_name='joystory.json',
                mime='application/json'
            )