Update app.py
Browse files
app.py
CHANGED
@@ -4,6 +4,81 @@ from datetime import datetime
|
|
4 |
import json
|
5 |
from collections import defaultdict
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
# Initialize OpenAI
|
8 |
openai.api_key = st.secrets["OPENAI_API_KEY"]
|
9 |
|
@@ -25,16 +100,37 @@ if 'vocabulary_used' not in st.session_state:
|
|
25 |
|
26 |
def get_ai_story_continuation(story_context, level):
|
27 |
"""Get story continuation from OpenAI API"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
try:
|
29 |
response = openai.ChatCompletion.create(
|
30 |
-
model="gpt-
|
31 |
messages=[
|
32 |
{"role": "system", "content": f"""You are helping a {level} level English student write a story.
|
|
|
|
|
|
|
33 |
Provide a JSON response with:
|
34 |
-
1. continuation: A single sentence continuing the story (
|
35 |
-
2. creative_prompt: A short question to guide the student's next sentence
|
36 |
-
3. vocabulary_suggestions: 3 relevant words they might use next
|
37 |
-
|
|
|
|
|
38 |
{"role": "user", "content": f"Story context: {story_context}\n\nProvide the next part of the story and a guiding question."}
|
39 |
],
|
40 |
temperature=0.7
|
@@ -45,20 +141,43 @@ def get_ai_story_continuation(story_context, level):
|
|
45 |
return {
|
46 |
"continuation": "The story continues...",
|
47 |
"creative_prompt": "What happens next?",
|
48 |
-
"vocabulary_suggestions": ["word1", "word2", "word3"]
|
|
|
49 |
}
|
50 |
|
51 |
def start_story(level):
|
52 |
"""Get the first sentence from AI"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
try:
|
54 |
response = openai.ChatCompletion.create(
|
55 |
-
model="gpt-4o-mini",
|
56 |
messages=[
|
57 |
{"role": "system", "content": f"""You are starting a story for a {level} level English student.
|
|
|
|
|
|
|
58 |
Provide a JSON response with:
|
59 |
-
1. opening: An engaging opening sentence (
|
60 |
-
2. creative_prompt: A short question to guide the student's first sentence
|
61 |
-
3. vocabulary_suggestions: 3 words they might use in their response
|
|
|
|
|
|
|
62 |
{"role": "user", "content": "Please start a new story."}
|
63 |
],
|
64 |
temperature=0.7
|
@@ -69,116 +188,422 @@ def start_story(level):
|
|
69 |
return {
|
70 |
"opening": "Once upon a time...",
|
71 |
"creative_prompt": "Who is our main character?",
|
72 |
-
"vocabulary_suggestions": ["brave", "curious", "friendly"]
|
|
|
73 |
}
|
74 |
|
75 |
def main():
|
76 |
st.title("π¨ Interactive Story Adventure")
|
77 |
|
78 |
-
#
|
79 |
if not st.session_state.story_started:
|
80 |
-
|
81 |
-
|
82 |
st.write("### Welcome to Story Adventure!")
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
key=
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
'timestamp': datetime.now().isoformat()
|
112 |
-
})
|
113 |
-
st.session_state.current_turn = 'Player'
|
114 |
-
|
115 |
-
st.rerun()
|
116 |
|
117 |
-
# Main story interface
|
118 |
if st.session_state.story_started:
|
119 |
-
# Story
|
120 |
with st.container():
|
121 |
-
st.
|
|
|
122 |
for entry in st.session_state.story:
|
123 |
-
if entry['author'] == 'AI'
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
st.
|
|
|
|
|
128 |
|
129 |
-
#
|
130 |
if st.session_state.current_turn == 'Player':
|
131 |
with st.container():
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
st.write(f"- {word}")
|
137 |
-
|
138 |
-
user_input = st.text_area("Your turn! Add to the story:", key='story_input')
|
139 |
-
if st.button("Submit"):
|
140 |
if user_input:
|
141 |
-
|
142 |
-
st.session_state.story.append({
|
143 |
-
'author': 'Player',
|
144 |
-
'text': user_input,
|
145 |
-
'timestamp': datetime.now().isoformat()
|
146 |
-
})
|
147 |
-
|
148 |
-
# Get AI's response
|
149 |
-
story_context = " ".join([entry['text'] for entry in st.session_state.story])
|
150 |
-
ai_response = get_ai_story_continuation(story_context, st.session_state.current_level)
|
151 |
-
|
152 |
-
# Add AI's response
|
153 |
-
st.session_state.story.append({
|
154 |
-
'author': 'AI',
|
155 |
-
'text': ai_response['continuation'],
|
156 |
-
'prompt': ai_response['creative_prompt'],
|
157 |
-
'vocabulary': ai_response['vocabulary_suggestions'],
|
158 |
-
'timestamp': datetime.now().isoformat()
|
159 |
-
})
|
160 |
-
|
161 |
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
162 |
|
163 |
-
#
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
)
|
|
|
174 |
|
175 |
-
|
176 |
-
|
177 |
-
st.
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
|
183 |
if __name__ == "__main__":
|
184 |
main()
|
|
|
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 |
|
|
|
100 |
|
101 |
def get_ai_story_continuation(story_context, level):
|
102 |
"""Get story continuation from OpenAI API"""
|
103 |
+
# Define level-specific parameters
|
104 |
+
level_params = {
|
105 |
+
'beginner': {
|
106 |
+
'complexity': "Use simple present tense and basic vocabulary. Keep sentences short (8-12 words).",
|
107 |
+
'examples': "Example: 'The dog runs to the park.' or 'She likes to read books.'"
|
108 |
+
},
|
109 |
+
'intermediate': {
|
110 |
+
'complexity': "Use present and past tense, compound sentences, and intermediate vocabulary.",
|
111 |
+
'examples': "Example: 'The excited dog rushed to the park, where his friends were waiting.'"
|
112 |
+
},
|
113 |
+
'advanced': {
|
114 |
+
'complexity': "Use varied tenses, complex sentences, and advanced vocabulary appropriately.",
|
115 |
+
'examples': "Example: 'Having spotted his friends at the park, the enthusiastic dog bounded over with unbridled joy.'"
|
116 |
+
}
|
117 |
+
}
|
118 |
+
|
119 |
try:
|
120 |
response = openai.ChatCompletion.create(
|
121 |
+
model="gpt-4o-mini", # ΰΈ«ΰΈ£ΰΈ·ΰΈ model ΰΈΰΈ΅ΰΉΰΈΰΈΉΰΈΰΈΰΉΰΈΰΈΰΈΰΈ²ΰΈ‘ΰΈΰΈ΅ΰΉΰΈΰΈΈΰΈΰΉΰΈΰΉ
|
122 |
messages=[
|
123 |
{"role": "system", "content": f"""You are helping a {level} level English student write a story.
|
124 |
+
{level_params[level]['complexity']}
|
125 |
+
{level_params[level]['examples']}
|
126 |
+
|
127 |
Provide a JSON response with:
|
128 |
+
1. continuation: A single sentence continuing the story (matching the specified level)
|
129 |
+
2. creative_prompt: A short question (3-8 words) to guide the student's next sentence
|
130 |
+
3. vocabulary_suggestions: 3 relevant words they might use next (appropriate for {level} level)
|
131 |
+
4. translation: Thai translation of the continuation sentence
|
132 |
+
|
133 |
+
Ensure the story stays coherent with the context."""},
|
134 |
{"role": "user", "content": f"Story context: {story_context}\n\nProvide the next part of the story and a guiding question."}
|
135 |
],
|
136 |
temperature=0.7
|
|
|
141 |
return {
|
142 |
"continuation": "The story continues...",
|
143 |
"creative_prompt": "What happens next?",
|
144 |
+
"vocabulary_suggestions": ["word1", "word2", "word3"],
|
145 |
+
"translation": "ΰΉΰΈ£ΰΈ·ΰΉΰΈΰΈΰΈ£ΰΈ²ΰΈ§ΰΈΰΈ³ΰΉΰΈΰΈ΄ΰΈΰΈΰΉΰΈΰΉΰΈ..."
|
146 |
}
|
147 |
|
148 |
def start_story(level):
|
149 |
"""Get the first sentence from AI"""
|
150 |
+
# Define level-specific starting prompts
|
151 |
+
level_prompts = {
|
152 |
+
'beginner': {
|
153 |
+
'guidance': "Start with a simple, engaging sentence about a person, animal, or place.",
|
154 |
+
'examples': "Example: 'A happy dog lived in a blue house.'"
|
155 |
+
},
|
156 |
+
'intermediate': {
|
157 |
+
'guidance': "Start with an interesting situation using compound sentences.",
|
158 |
+
'examples': "Example: 'On a sunny morning, Sarah found a mysterious letter in her garden.'"
|
159 |
+
},
|
160 |
+
'advanced': {
|
161 |
+
'guidance': "Start with a sophisticated hook using complex sentence structure.",
|
162 |
+
'examples': "Example: 'Deep in the ancient forest, where shadows danced beneath ancient trees, a peculiar sound echoed through the mist.'"
|
163 |
+
}
|
164 |
+
}
|
165 |
+
|
166 |
try:
|
167 |
response = openai.ChatCompletion.create(
|
168 |
+
model="gpt-4o-mini", # ΰΈ«ΰΈ£ΰΈ·ΰΈ model ΰΈΰΈ΅ΰΉΰΈΰΈΉΰΈΰΈΰΉΰΈΰΈΰΈΰΈ²ΰΈ‘ΰΈΰΈ΅ΰΉΰΈΰΈΈΰΈΰΉΰΈΰΉ
|
169 |
messages=[
|
170 |
{"role": "system", "content": f"""You are starting a story for a {level} level English student.
|
171 |
+
{level_prompts[level]['guidance']}
|
172 |
+
{level_prompts[level]['examples']}
|
173 |
+
|
174 |
Provide a JSON response with:
|
175 |
+
1. opening: An engaging opening sentence (matching the specified level)
|
176 |
+
2. creative_prompt: A short question (3-8 words) to guide the student's first sentence
|
177 |
+
3. vocabulary_suggestions: 3 words they might use in their response (appropriate for {level} level)
|
178 |
+
4. translation: Thai translation of the opening sentence
|
179 |
+
|
180 |
+
Make the opening engaging but appropriate for the student's level."""},
|
181 |
{"role": "user", "content": "Please start a new story."}
|
182 |
],
|
183 |
temperature=0.7
|
|
|
188 |
return {
|
189 |
"opening": "Once upon a time...",
|
190 |
"creative_prompt": "Who is our main character?",
|
191 |
+
"vocabulary_suggestions": ["brave", "curious", "friendly"],
|
192 |
+
"translation": "ΰΈΰΈ²ΰΈ₯ΰΈΰΈ£ΰΈ±ΰΉΰΈΰΈ«ΰΈΰΈΆΰΉΰΈ..."
|
193 |
}
|
194 |
|
195 |
def main():
|
196 |
st.title("π¨ Interactive Story Adventure")
|
197 |
|
198 |
+
# Initial setup (level selection and first turn choice)
|
199 |
if not st.session_state.story_started:
|
200 |
+
with st.container():
|
201 |
+
st.markdown('<div class="box-container">', unsafe_allow_html=True)
|
202 |
st.write("### Welcome to Story Adventure!")
|
203 |
+
col1, col2 = st.columns(2)
|
204 |
+
with col1:
|
205 |
+
level = st.selectbox(
|
206 |
+
"Choose your English level:",
|
207 |
+
['beginner', 'intermediate', 'advanced']
|
208 |
+
)
|
209 |
+
with col2:
|
210 |
+
first_turn = st.radio(
|
211 |
+
"Who should start the story?",
|
212 |
+
['AI', 'Player']
|
213 |
+
)
|
214 |
+
if st.button("Start Story!", key="start_button"):
|
215 |
+
st.session_state.current_level = level
|
216 |
+
st.session_state.first_turn = first_turn
|
217 |
+
st.session_state.current_turn = first_turn
|
218 |
+
st.session_state.story_started = True
|
219 |
+
if first_turn == 'AI':
|
220 |
+
response = start_story(level)
|
221 |
+
st.session_state.story.append({
|
222 |
+
'author': 'AI',
|
223 |
+
'text': response['opening'],
|
224 |
+
'prompt': response['creative_prompt'],
|
225 |
+
'vocabulary': response['vocabulary_suggestions'],
|
226 |
+
'timestamp': datetime.now().isoformat()
|
227 |
+
})
|
228 |
+
st.session_state.current_turn = 'Player'
|
229 |
+
st.rerun()
|
230 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
231 |
|
232 |
+
# Main story interface with 4-box layout
|
233 |
if st.session_state.story_started:
|
234 |
+
# Box 1: Story Display
|
235 |
with st.container():
|
236 |
+
st.markdown('<div class="box-container story-box">', unsafe_allow_html=True)
|
237 |
+
st.write("### π Our Story So Far:")
|
238 |
for entry in st.session_state.story:
|
239 |
+
css_class = "ai-text" if entry['author'] == 'AI' else "player-text"
|
240 |
+
st.markdown(f'<div class="story-text {css_class}">{entry["text"]}</div>',
|
241 |
+
unsafe_allow_html=True)
|
242 |
+
if 'prompt' in entry:
|
243 |
+
st.markdown(f'<div class="prompt-text">π€ {entry["prompt"]}</div>',
|
244 |
+
unsafe_allow_html=True)
|
245 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
246 |
|
247 |
+
# Box 2: Writing Area
|
248 |
if st.session_state.current_turn == 'Player':
|
249 |
with st.container():
|
250 |
+
st.markdown('<div class="box-container input-box">', unsafe_allow_html=True)
|
251 |
+
st.write("### βοΈ Your Turn!")
|
252 |
+
user_input = st.text_area("Write your next sentence:", key='story_input')
|
253 |
+
if st.button("β¨ Submit", key="submit_button"):
|
|
|
|
|
|
|
|
|
254 |
if user_input:
|
255 |
+
process_player_input(user_input)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
st.rerun()
|
257 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
258 |
+
|
259 |
+
# Box 3: Help Buttons
|
260 |
+
with st.container():
|
261 |
+
st.markdown('<div class="box-container help-box">', unsafe_allow_html=True)
|
262 |
+
st.write("### π‘ Need Help?")
|
263 |
+
col1, col2, col3 = st.columns(3)
|
264 |
+
with col1:
|
265 |
+
if st.button("π Vocabulary Help"):
|
266 |
+
show_vocabulary_help()
|
267 |
+
with col2:
|
268 |
+
if st.button("β Writing Tips"):
|
269 |
+
show_writing_tips()
|
270 |
+
with col3:
|
271 |
+
if st.button("π― Story Ideas"):
|
272 |
+
show_story_ideas()
|
273 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
274 |
+
|
275 |
+
# Box 4: Rewards and Progress
|
276 |
+
with st.container():
|
277 |
+
st.markdown('<div class="box-container rewards-box">', unsafe_allow_html=True)
|
278 |
+
st.write("### π Your Achievements")
|
279 |
+
show_achievements()
|
280 |
+
col1, col2 = st.columns(2)
|
281 |
+
with col1:
|
282 |
+
if st.button("π₯ Save Story"):
|
283 |
+
save_story()
|
284 |
+
with col2:
|
285 |
+
if st.button("π Start New Story"):
|
286 |
+
reset_story()
|
287 |
+
st.rerun()
|
288 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
289 |
+
|
290 |
+
def process_player_input(user_input):
|
291 |
+
"""Process the player's input, generate feedback, and update the story"""
|
292 |
+
try:
|
293 |
+
# Get story context for AI
|
294 |
+
story_context = " ".join([entry['text'] for entry in st.session_state.story])
|
295 |
|
296 |
+
# Get writing feedback from GPT
|
297 |
+
feedback_response = openai.ChatCompletion.create(
|
298 |
+
model="gpt-4o-mini", # ΰΈ«ΰΈ£ΰΈ·ΰΈ model ΰΈΰΈ΅ΰΉΰΈΰΈΉΰΈΰΈΰΉΰΈΰΈΰΈΰΈ²ΰΈ‘ΰΈΰΈ΅ΰΉΰΈΰΈΈΰΈΰΉΰΈΰΉ
|
299 |
+
messages=[
|
300 |
+
{"role": "system", "content": f"""You are an English teacher helping a {st.session_state.current_level} level student.
|
301 |
+
Analyze their sentence and provide feedback in JSON format with:
|
302 |
+
1. grammar_check: List of grammar issues found (if any)
|
303 |
+
2. spelling_check: List of spelling issues found (if any)
|
304 |
+
3. improvement_suggestions: Specific suggestions for improvement
|
305 |
+
4. positive_feedback: What they did well
|
306 |
+
Be encouraging but thorough in your feedback."""},
|
307 |
+
{"role": "user", "content": f"Student's sentence: {user_input}"}
|
308 |
+
],
|
309 |
+
temperature=0.7
|
310 |
+
)
|
311 |
+
feedback = json.loads(feedback_response.choices[0].message.content)
|
312 |
+
|
313 |
+
# Add player's sentence to story with feedback
|
314 |
+
st.session_state.story.append({
|
315 |
+
'author': 'Player',
|
316 |
+
'text': user_input,
|
317 |
+
'feedback': feedback,
|
318 |
+
'timestamp': datetime.now().isoformat()
|
319 |
+
})
|
320 |
+
|
321 |
+
# Update vocabulary used
|
322 |
+
words = set(user_input.lower().split())
|
323 |
+
st.session_state.vocabulary_used.update(words)
|
324 |
+
|
325 |
+
# Check for achievements
|
326 |
+
check_achievements(user_input, feedback)
|
327 |
+
|
328 |
+
# Get AI's continuation
|
329 |
+
ai_response = get_ai_story_continuation(
|
330 |
+
story_context + "\n" + user_input,
|
331 |
+
st.session_state.current_level
|
332 |
+
)
|
333 |
+
|
334 |
+
# Add AI's response to story
|
335 |
+
st.session_state.story.append({
|
336 |
+
'author': 'AI',
|
337 |
+
'text': ai_response['continuation'],
|
338 |
+
'prompt': ai_response['creative_prompt'],
|
339 |
+
'vocabulary': ai_response['vocabulary_suggestions'],
|
340 |
+
'translation': ai_response.get('translation', ''),
|
341 |
+
'timestamp': datetime.now().isoformat()
|
342 |
+
})
|
343 |
+
|
344 |
+
# Switch turn back to player
|
345 |
+
st.session_state.current_turn = 'Player'
|
346 |
+
|
347 |
+
# Show feedback to player
|
348 |
+
display_feedback(feedback)
|
349 |
+
|
350 |
+
except Exception as e:
|
351 |
+
st.error(f"Error processing input: {e}")
|
352 |
+
st.session_state.story.append({
|
353 |
+
'author': 'Player',
|
354 |
+
'text': user_input,
|
355 |
+
'timestamp': datetime.now().isoformat()
|
356 |
+
})
|
357 |
+
|
358 |
+
def check_achievements(user_input, feedback):
|
359 |
+
"""Check and award achievements based on player's writing"""
|
360 |
+
# Story length achievements
|
361 |
+
if len(st.session_state.story) >= 10:
|
362 |
+
award_badge("Storyteller")
|
363 |
+
if len(st.session_state.story) >= 20:
|
364 |
+
award_badge("Master Storyteller")
|
365 |
+
|
366 |
+
# Vocabulary achievements
|
367 |
+
if len(st.session_state.vocabulary_used) >= 20:
|
368 |
+
award_badge("Vocabulary Explorer")
|
369 |
+
if len(st.session_state.vocabulary_used) >= 50:
|
370 |
+
award_badge("Word Master")
|
371 |
+
|
372 |
+
# Writing quality achievements
|
373 |
+
if not feedback['grammar_check'] and not feedback['spelling_check']:
|
374 |
+
award_badge("Perfect Writing")
|
375 |
+
|
376 |
+
# Creativity achievements
|
377 |
+
if len(user_input.split()) >= 15:
|
378 |
+
award_badge("Detailed Writer")
|
379 |
+
|
380 |
+
def award_badge(badge_name):
|
381 |
+
"""Award a new badge if not already earned"""
|
382 |
+
if badge_name not in st.session_state.badges:
|
383 |
+
st.session_state.badges[badge_name] = 1
|
384 |
+
st.balloons()
|
385 |
+
st.success(f"π New Achievement Unlocked: {badge_name}!")
|
386 |
+
else:
|
387 |
+
st.session_state.badges[badge_name] += 1
|
388 |
+
|
389 |
+
def display_feedback(feedback):
|
390 |
+
"""Display writing feedback to the player"""
|
391 |
+
with st.container():
|
392 |
+
# Show positive feedback first
|
393 |
+
st.success(f"π {feedback['positive_feedback']}")
|
394 |
+
|
395 |
+
# Show any grammar or spelling issues
|
396 |
+
if feedback['grammar_check']:
|
397 |
+
st.warning("π Grammar points to consider:")
|
398 |
+
for issue in feedback['grammar_check']:
|
399 |
+
st.write(f"- {issue}")
|
400 |
+
|
401 |
+
if feedback['spelling_check']:
|
402 |
+
st.warning("βοΈ Spelling points to consider:")
|
403 |
+
for issue in feedback['spelling_check']:
|
404 |
+
st.write(f"- {issue}")
|
405 |
+
|
406 |
+
# Show improvement suggestions
|
407 |
+
if feedback['improvement_suggestions']:
|
408 |
+
st.info("π‘ Suggestions for next time:")
|
409 |
+
for suggestion in feedback['improvement_suggestions']:
|
410 |
+
st.write(f"- {suggestion}")
|
411 |
+
|
412 |
+
def show_vocabulary_help():
|
413 |
+
"""Show vocabulary suggestions and meanings"""
|
414 |
+
if st.session_state.story:
|
415 |
+
last_entry = st.session_state.story[-1]
|
416 |
+
if 'vocabulary' in last_entry:
|
417 |
+
# Get word meanings and examples
|
418 |
+
try:
|
419 |
+
word_info = openai.ChatCompletion.create(
|
420 |
+
model="gpt-4o-mini",
|
421 |
+
messages=[
|
422 |
+
{"role": "system", "content": f"""For each word, provide:
|
423 |
+
1. Simple definition
|
424 |
+
2. Thai translation
|
425 |
+
3. Example sentence
|
426 |
+
Format in JSON with word as key."""},
|
427 |
+
{"role": "user", "content": f"Words: {', '.join(last_entry['vocabulary'])}"}
|
428 |
+
]
|
429 |
)
|
430 |
+
word_details = json.loads(word_info.choices[0].message.content)
|
431 |
|
432 |
+
st.write("### π Suggested Words:")
|
433 |
+
for word in last_entry['vocabulary']:
|
434 |
+
with st.expander(f"π€ {word}"):
|
435 |
+
if word in word_details:
|
436 |
+
details = word_details[word]
|
437 |
+
st.write(f"**Meaning:** {details['definition']}")
|
438 |
+
st.write(f"**ΰΉΰΈΰΈ₯ΰΉΰΈΰΈ’:** {details['thai']}")
|
439 |
+
st.write(f"**Example:** _{details['example']}_")
|
440 |
+
except Exception as e:
|
441 |
+
# Fallback to simple display
|
442 |
+
st.write("Try using these words:")
|
443 |
+
for word in last_entry['vocabulary']:
|
444 |
+
st.markdown(f"- *{word}*")
|
445 |
+
|
446 |
+
def show_writing_tips():
|
447 |
+
"""Show writing tips based on current level and story context"""
|
448 |
+
if st.session_state.story:
|
449 |
+
try:
|
450 |
+
# Get personalized writing tips
|
451 |
+
tips_response = openai.ChatCompletion.create(
|
452 |
+
model="gpt-4o-mini",
|
453 |
+
messages=[
|
454 |
+
{"role": "system", "content": f"""You are helping a {st.session_state.current_level} level English student.
|
455 |
+
Provide 3 specific writing tips based on their level and story context.
|
456 |
+
Include examples for each tip.
|
457 |
+
Keep tips short and friendly."""},
|
458 |
+
{"role": "user", "content": f"Story so far: {' '.join([entry['text'] for entry in st.session_state.story])}"}
|
459 |
+
]
|
460 |
+
)
|
461 |
+
|
462 |
+
st.write("### βοΈ Writing Tips")
|
463 |
+
tips = tips_response.choices[0].message.content.split('\n')
|
464 |
+
for tip in tips:
|
465 |
+
if tip.strip():
|
466 |
+
st.info(tip)
|
467 |
+
|
468 |
+
# Add quick reference examples
|
469 |
+
st.write("### π― Quick Examples:")
|
470 |
+
col1, col2 = st.columns(2)
|
471 |
+
with col1:
|
472 |
+
st.write("**Good:**")
|
473 |
+
st.success("The happy dog played in the garden.")
|
474 |
+
with col2:
|
475 |
+
st.write("**Better:**")
|
476 |
+
st.success("The excited golden retriever chased butterflies in the colorful garden.")
|
477 |
+
|
478 |
+
except Exception as e:
|
479 |
+
# Fallback tips
|
480 |
+
st.write("### General Writing Tips:")
|
481 |
+
st.write("1. Use descriptive words")
|
482 |
+
st.write("2. Keep your sentences clear")
|
483 |
+
st.write("3. Try to be creative")
|
484 |
+
|
485 |
+
def show_story_ideas():
|
486 |
+
"""Generate creative story ideas based on current context"""
|
487 |
+
if st.session_state.story:
|
488 |
+
try:
|
489 |
+
# Get creative prompts
|
490 |
+
ideas_response = openai.ChatCompletion.create(
|
491 |
+
model="gpt-4o-mini",
|
492 |
+
messages=[
|
493 |
+
{"role": "system", "content": f"""Generate 3 creative story direction ideas for a {st.session_state.current_level} student.
|
494 |
+
Ideas should follow from the current story.
|
495 |
+
Make suggestions exciting but achievable for their level."""},
|
496 |
+
{"role": "user", "content": f"Story so far: {' '.join([entry['text'] for entry in st.session_state.story])}"}
|
497 |
+
]
|
498 |
+
)
|
499 |
+
|
500 |
+
st.write("### π Story Ideas")
|
501 |
+
ideas = ideas_response.choices[0].message.content.split('\n')
|
502 |
+
for i, idea in enumerate(ideas, 1):
|
503 |
+
if idea.strip():
|
504 |
+
with st.expander(f"Idea {i}"):
|
505 |
+
st.write(idea)
|
506 |
+
|
507 |
+
# Add story element suggestions
|
508 |
+
st.write("### π¨ Try adding:")
|
509 |
+
elements = ["a new character", "a surprise event", "a funny moment", "a problem to solve"]
|
510 |
+
cols = st.columns(len(elements))
|
511 |
+
for col, element in zip(cols, elements):
|
512 |
+
with col:
|
513 |
+
st.button(element, key=f"element_{element}")
|
514 |
+
|
515 |
+
except Exception as e:
|
516 |
+
# Fallback ideas
|
517 |
+
st.write("### Story Ideas:")
|
518 |
+
st.write("1. Introduce a new character")
|
519 |
+
st.write("2. Add an unexpected event")
|
520 |
+
st.write("3. Create a problem to solve")
|
521 |
+
|
522 |
+
def show_achievements():
|
523 |
+
"""Display achievements and badges with animations"""
|
524 |
+
if st.session_state.badges:
|
525 |
+
st.write("### π Your Achievements")
|
526 |
+
cols = st.columns(3)
|
527 |
+
for i, (badge, count) in enumerate(st.session_state.badges.items()):
|
528 |
+
with cols[i % 3]:
|
529 |
+
st.markdown(f'''
|
530 |
+
<div class="badge-container" style="animation: bounce 1s infinite;">
|
531 |
+
<div style="font-size: 2em;">π</div>
|
532 |
+
<div style="font-weight: bold;">{badge}</div>
|
533 |
+
<div>x{count}</div>
|
534 |
+
</div>
|
535 |
+
''', unsafe_allow_html=True)
|
536 |
+
|
537 |
+
# Add progress towards next badge
|
538 |
+
st.write("### π Progress to Next Badge:")
|
539 |
+
current_words = len(st.session_state.vocabulary_used)
|
540 |
+
next_milestone = (current_words // 10 + 1) * 10
|
541 |
+
progress = current_words / next_milestone
|
542 |
+
st.progress(progress)
|
543 |
+
st.write(f"Use {next_milestone - current_words} more unique words for next badge!")
|
544 |
+
|
545 |
+
def save_story():
|
546 |
+
"""Save story with additional information"""
|
547 |
+
if st.session_state.story:
|
548 |
+
# Prepare story text with formatting
|
549 |
+
story_parts = []
|
550 |
+
story_parts.append("=== My English Story Adventure ===\n")
|
551 |
+
story_parts.append(f"Level: {st.session_state.current_level}")
|
552 |
+
story_parts.append(f"Date: {datetime.now().strftime('%Y-%m-%d')}\n")
|
553 |
+
|
554 |
+
# Add story content
|
555 |
+
for entry in st.session_state.story:
|
556 |
+
author = "π€ You:" if entry['author'] == 'Player' else "π€ AI:"
|
557 |
+
story_parts.append(f"{author} {entry['text']}")
|
558 |
+
|
559 |
+
# Add achievements
|
560 |
+
story_parts.append("\n=== Achievements ===")
|
561 |
+
for badge, count in st.session_state.badges.items():
|
562 |
+
story_parts.append(f"π {badge}: x{count}")
|
563 |
+
|
564 |
+
# Create the full story text
|
565 |
+
story_text = "\n".join(story_parts)
|
566 |
+
|
567 |
+
# Offer download options
|
568 |
+
col1, col2 = st.columns(2)
|
569 |
+
with col1:
|
570 |
+
st.download_button(
|
571 |
+
label="π Download Story (Text)",
|
572 |
+
data=story_text,
|
573 |
+
file_name="my_story.txt",
|
574 |
+
mime="text/plain"
|
575 |
+
)
|
576 |
+
with col2:
|
577 |
+
# Create JSON version with more details
|
578 |
+
story_data = {
|
579 |
+
"metadata": {
|
580 |
+
"level": st.session_state.current_level,
|
581 |
+
"date": datetime.now().isoformat(),
|
582 |
+
"vocabulary_used": list(st.session_state.vocabulary_used),
|
583 |
+
"achievements": dict(st.session_state.badges)
|
584 |
+
},
|
585 |
+
"story": st.session_state.story
|
586 |
+
}
|
587 |
+
st.download_button(
|
588 |
+
label="πΎ Download Progress Report (JSON)",
|
589 |
+
data=json.dumps(story_data, indent=2),
|
590 |
+
file_name="story_progress.json",
|
591 |
+
mime="application/json"
|
592 |
+
)
|
593 |
+
|
594 |
+
def reset_story():
|
595 |
+
"""Reset all story-related session state"""
|
596 |
+
if st.button("π Start New Story", key="reset_button"):
|
597 |
+
st.session_state.story = []
|
598 |
+
st.session_state.story_started = False
|
599 |
+
st.session_state.first_turn = None
|
600 |
+
st.session_state.current_turn = None
|
601 |
+
st.session_state.vocabulary_used = set()
|
602 |
+
# Keep badges for long-term progress
|
603 |
+
st.balloons()
|
604 |
+
st.success("Ready for a new adventure! Your achievements have been saved.")
|
605 |
+
st.rerun()
|
606 |
+
|
607 |
|
608 |
if __name__ == "__main__":
|
609 |
main()
|