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'
) |