|
import streamlit as st |
|
import json |
|
import datetime |
|
from openai import OpenAI |
|
from typing import Dict, List, Set |
|
|
|
|
|
st.set_page_config( |
|
page_title="JoyStory - Interactive Story Adventure", |
|
page_icon="📖", |
|
layout="wide", |
|
initial_sidebar_state="collapsed", |
|
) |
|
|
|
|
|
client = OpenAI() |
|
|
|
|
|
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) |
|
|
|
|
|
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"] |
|
|
|
|
|
if st.button("Get Vocabulary Ideas"): |
|
vocab_suggestions = get_vocabulary_suggestions() |
|
st.markdown("#### 📚 Suggested Words") |
|
for word in vocab_suggestions: |
|
st.markdown(f"• *{word}*") |
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
|
|
|
|
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 "🌟 พยายามได้ดีมากค่ะ/ครับ ลองเขียนต่อไปนะ!" |
|
|
|
|
|
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) |
|
|
|
|
|
""" |
|
🎯 คำว่า 'tree' เมื่อพูดถึงหลายต้น ต้องเติม s เป็น 'trees' นะคะ |
|
|
|
ตัวอย่าง: |
|
- ถูก: There are many trees in the forest. |
|
- ผิด: There are many tree in the forest. |
|
|
|
เขียนได้ดีมากเลยค่ะ ประโยคสื่อความหมายได้ชัดเจน! ✨ |
|
""" |
|
|
|
|
|
""" |
|
💭 ต่อไปตัวละครจะไปไหน? |
|
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 |
|
|
|
|
|
if st.session_state.should_reset: |
|
reset_story() |
|
|
|
|
|
st.markdown("# 📖 JoyStory") |
|
show_welcome_section() |
|
|
|
|
|
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() |
|
|
|
|
|
col1, col2 = st.columns([3, 1]) |
|
|
|
with col1: |
|
|
|
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']) |
|
|
|
|
|
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: |
|
|
|
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}*") |
|
|
|
|
|
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) |
|
|
|
|
|
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!") |
|
|
|
|
|
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' |
|
) |