Rathapoom commited on
Commit
912046e
·
verified ·
1 Parent(s): 2ab6350

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +710 -1
app.py CHANGED
@@ -929,4 +929,713 @@ def show_theme_card(theme: Dict):
929
  st.rerun()
930
  except Exception as e:
931
  logging.error(f"Error selecting theme: {str(e)}")
932
- st.error("เกิดข้อผิดพลาดในการเลือกธีม กรุณาลองใหม่อีกครั้ง")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
929
  st.rerun()
930
  except Exception as e:
931
  logging.error(f"Error selecting theme: {str(e)}")
932
+ st.error("เกิดข้อผิดพลาดในการเลือกธีม กรุณาลองใหม่อีกครั้ง")
933
+
934
+ def show_story():
935
+ """Display the story with proper formatting"""
936
+ story_display = st.container()
937
+
938
+ with story_display:
939
+ if not st.session_state.story:
940
+ st.info("เลือกธีมเรื่องราวที่ต้องการเพื่อเริ่มต้นการผจญภัย!")
941
+ return
942
+
943
+ # Story Display Box
944
+ for idx, entry in enumerate(st.session_state.story):
945
+ if entry['role'] == 'AI':
946
+ if entry.get('is_starter'):
947
+ # Story Starter
948
+ st.markdown(f"""
949
+ <div style="
950
+ background-color: #f0f7ff;
951
+ padding: 20px;
952
+ border-radius: 10px;
953
+ margin: 10px 0;
954
+ border-left: 4px solid #1e88e5;
955
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
956
+ ">
957
+ <div style="color: #1e88e5; margin-bottom: 10px; font-size: 1.1em;">
958
+ 🎬 เริ่มเรื่อง:
959
+ </div>
960
+ <div style="color: #666; margin-bottom: 8px; font-family: 'Sarabun', sans-serif;">
961
+ {entry.get('thai_content', '')}
962
+ </div>
963
+ <div style="color: #333; font-size: 1.1em;">
964
+ {entry['content']}
965
+ </div>
966
+ </div>
967
+ """, unsafe_allow_html=True)
968
+ else:
969
+ # AI Response
970
+ st.markdown(f"""
971
+ <div style="
972
+ background-color: #f8f9fa;
973
+ padding: 15px;
974
+ border-radius: 8px;
975
+ margin: 8px 0;
976
+ border-left: 4px solid #4caf50;
977
+ ">
978
+ <div style="color: #2e7d32;">
979
+ 🤖 AI: {entry['content']}
980
+ </div>
981
+ </div>
982
+ """, unsafe_allow_html=True)
983
+
984
+ elif entry['role'] == 'You':
985
+ # User Entry
986
+ status_icon = "✅" if entry.get('is_correct') else "✍️"
987
+ bg_color = "#e8f5e9" if entry.get('is_correct') else "#fff"
988
+ border_color = "#4caf50" if entry.get('is_correct') else "#1e88e5"
989
+
990
+ st.markdown(f"""
991
+ <div style="
992
+ background-color: {bg_color};
993
+ padding: 15px;
994
+ border-radius: 8px;
995
+ margin: 8px 0;
996
+ border-left: 4px solid {border_color};
997
+ ">
998
+ <div style="color: #333;">
999
+ 👤 You: {status_icon} {entry['content']}
1000
+ </div>
1001
+ {f'<div style="font-size: 0.9em; color: #666; margin-top: 5px;">{entry.get("feedback", "")}</div>' if entry.get('feedback') else ''}
1002
+ </div>
1003
+ """, unsafe_allow_html=True)
1004
+
1005
+ def show_feedback_section():
1006
+ """Display writing feedback section"""
1007
+ if not st.session_state.feedback:
1008
+ return
1009
+
1010
+ st.markdown("""
1011
+ <div style="margin-bottom: 20px;">
1012
+ <div class="thai-eng">
1013
+ <div class="thai">📝 คำแนะนำจากครู</div>
1014
+ <div class="eng">Writing Feedback</div>
1015
+ </div>
1016
+ </div>
1017
+ """, unsafe_allow_html=True)
1018
+
1019
+ feedback_data = st.session_state.feedback
1020
+ if isinstance(feedback_data, dict) and feedback_data.get('has_errors'):
1021
+ # Show error feedback
1022
+ st.markdown(f"""
1023
+ <div style="
1024
+ background-color: #fff3e0;
1025
+ padding: 20px;
1026
+ border-radius: 10px;
1027
+ border-left: 4px solid #ff9800;
1028
+ margin: 10px 0;
1029
+ ">
1030
+ <p style="color: #e65100; margin-bottom: 15px;">
1031
+ {feedback_data['feedback']}
1032
+ </p>
1033
+ <div style="
1034
+ background-color: #fff;
1035
+ padding: 15px;
1036
+ border-radius: 8px;
1037
+ margin-top: 10px;
1038
+ ">
1039
+ <p style="color: #666; margin-bottom: 5px;">
1040
+ ประโยคที่ถูกต้อง:
1041
+ </p>
1042
+ <p style="
1043
+ color: #2e7d32;
1044
+ font-weight: 500;
1045
+ font-size: 1.1em;
1046
+ margin: 0;
1047
+ ">
1048
+ {feedback_data['corrected']}
1049
+ </p>
1050
+ </div>
1051
+ </div>
1052
+ """, unsafe_allow_html=True)
1053
+
1054
+ # Correction button
1055
+ if st.button(
1056
+ "✍️ แก้ไขประโยคให้ถูกต้อง",
1057
+ key="correct_button",
1058
+ help="คลิกเพื่อแก้ไขประโยคให้ถูกต้องตามคำแนะนำ"
1059
+ ):
1060
+ last_user_entry_idx = next(
1061
+ (i for i, entry in reversed(list(enumerate(st.session_state.story)))
1062
+ if entry['role'] == 'You'),
1063
+ None
1064
+ )
1065
+ if last_user_entry_idx is not None:
1066
+ apply_correction(last_user_entry_idx, feedback_data['corrected'])
1067
+ st.rerun()
1068
+ else:
1069
+ # Show success feedback
1070
+ st.markdown(f"""
1071
+ <div style="
1072
+ background-color: #e8f5e9;
1073
+ padding: 20px;
1074
+ border-radius: 10px;
1075
+ border-left: 4px solid #4caf50;
1076
+ margin: 10px 0;
1077
+ ">
1078
+ <p style="color: #2e7d32; margin: 0;">
1079
+ {feedback_data.get('feedback', '✨ เขียนได้ถูกต้องแล้วค่ะ!')}
1080
+ </p>
1081
+ </div>
1082
+ """, unsafe_allow_html=True)
1083
+
1084
+ def show_writing_tools():
1085
+ """Display writing tools section"""
1086
+ with st.expander("✨ เครื่องมือช่วยเขียน | Writing Tools"):
1087
+ col1, col2 = st.columns(2)
1088
+
1089
+ with col1:
1090
+ if st.button("🎯 ขอคำใบ้", use_container_width=True):
1091
+ with st.spinner("กำลังสร้างคำใบ้..."):
1092
+ prompt = get_creative_prompt()
1093
+ st.markdown(f"""
1094
+ <div style="
1095
+ background-color: #f3e5f5;
1096
+ padding: 15px;
1097
+ border-radius: 8px;
1098
+ margin-top: 10px;
1099
+ ">
1100
+ <div style="color: #6a1b9a;">💭 {prompt['thai']}</div>
1101
+ <div style="color: #666; font-style: italic;">
1102
+ 💭 {prompt['eng']}
1103
+ </div>
1104
+ </div>
1105
+ """, unsafe_allow_html=True)
1106
+
1107
+ with col2:
1108
+ if st.button("📚 คำศัพท์แนะนำ", use_container_width=True):
1109
+ with st.spinner("กำลังค้นหาคำศัพท์..."):
1110
+ vocab_suggestions = get_vocabulary_suggestions()
1111
+ st.markdown("#### 📚 คำศัพท์น่ารู้")
1112
+ for word in vocab_suggestions:
1113
+ st.markdown(f"""
1114
+ <div style="
1115
+ background-color: #f5f5f5;
1116
+ padding: 10px;
1117
+ border-radius: 5px;
1118
+ margin: 5px 0;
1119
+ ">
1120
+ • {word}
1121
+ </div>
1122
+ """, unsafe_allow_html=True)
1123
+
1124
+ def show_achievements():
1125
+ """Display achievements and stats"""
1126
+ with st.container():
1127
+ # Points and Streak
1128
+ st.markdown(f"""
1129
+ <div style="
1130
+ background-color: #e3f2fd;
1131
+ padding: 20px;
1132
+ border-radius: 10px;
1133
+ margin-bottom: 20px;
1134
+ text-align: center;
1135
+ ">
1136
+ <h3 style="color: #1e88e5; margin: 0;">
1137
+ 🌟 คะแนนรวม: {st.session_state.points['total']}
1138
+ </h3>
1139
+ <p style="margin: 5px 0;">
1140
+ 🔥 Streak ปัจจุบัน: {st.session_state.points['streak']} ประโยค
1141
+ </p>
1142
+ <p style="margin: 5px 0;">
1143
+ ⭐ Streak สูงสุด: {st.session_state.points['max_streak']} ประโยค
1144
+ </p>
1145
+ </div>
1146
+ """, unsafe_allow_html=True)
1147
+
1148
+ # Writing Stats
1149
+ st.markdown("""
1150
+ <div style="margin-bottom: 15px;">
1151
+ <h3>📊 สถิติการเขียน</h3>
1152
+ </div>
1153
+ """, unsafe_allow_html=True)
1154
+
1155
+ col1, col2 = st.columns(2)
1156
+ with col1:
1157
+ st.metric(
1158
+ "ประโยคที่เขียนทั้งหมด",
1159
+ st.session_state.stats['total_sentences'],
1160
+ help="จำนวนประโยคทั้งหมดที่คุณได้เขียน"
1161
+ )
1162
+ st.metric(
1163
+ "ถูกต้องตั้งแต่แรก",
1164
+ st.session_state.stats['correct_first_try'],
1165
+ help="จำนวนประโยคที่ถูกต้องโดยไม่ต้องแก้ไข"
1166
+ )
1167
+ with col2:
1168
+ st.metric(
1169
+ "ความแม่นยำ",
1170
+ f"{st.session_state.stats['accuracy_rate']:.1f}%",
1171
+ help="เปอร์เซ็นต์ของประโยคที่ถูกต้องตั้งแต่แรก"
1172
+ )
1173
+ st.metric(
1174
+ "คำศัพท์ที่ใช้",
1175
+ len(st.session_state.stats['vocabulary_used']),
1176
+ help="จำนวนคำศัพท์ที่ไม่ซ้ำกันที่คุณได้ใช้"
1177
+ )
1178
+
1179
+ # Show Achievements
1180
+ st.markdown("""
1181
+ <div style="margin: 25px 0 15px 0;">
1182
+ <h3>🏆 ความสำเร็จ</h3>
1183
+ </div>
1184
+ """, unsafe_allow_html=True)
1185
+
1186
+ if st.session_state.achievements:
1187
+ for achievement in st.session_state.achievements:
1188
+ st.success(achievement)
1189
+ else:
1190
+ st.info("ยังไม่มีความสำเร็จ - เขียนต่อไปเพื่อปลดล็อกรางวัล!")
1191
+
1192
+ # Show available achievements
1193
+ st.markdown("""
1194
+ <div style="
1195
+ margin-top: 15px;
1196
+ padding: 15px;
1197
+ background-color: #f5f5f5;
1198
+ border-radius: 8px;
1199
+ ">
1200
+ <p style="color: #666; margin-bottom: 10px;">
1201
+ รางวัลที่รอคุณอยู่:
1202
+ </p>
1203
+ <ul style="list-style-type: none; padding-left: 0;">
1204
+ <li>🌟 นักเขียนไร้ที่ติ: เขียนถูกต้อง 5 ประโยคติดต่อกัน</li>
1205
+ <li>📚 ราชาคำศัพท์: ใช้คำศัพท์ไม่ซ้ำกัน 50 คำ</li>
1206
+ <li>📖 นักแต่งนิทาน: เขียนเรื่องยาว 10 ประโยค</li>
1207
+ <li>👑 ราชาความแม่นยำ: มีอัตราความถูกต้อง 80% ขึ้นไป</li>
1208
+ </ul>
1209
+ </div>
1210
+ """, unsafe_allow_html=True)
1211
+
1212
+ def show_save_options():
1213
+ """Display save and export options"""
1214
+ st.markdown("### 💾 บันทึกเรื่องราว")
1215
+
1216
+ if not st.session_state.story:
1217
+ st.info("เริ่มเขียนเรื่องราวก่อนเพื่อบันทึกผลงานของคุณ")
1218
+ return
1219
+
1220
+ col1, col2 = st.columns(2)
1221
+ with col1:
1222
+ # PDF Export
1223
+ if st.button("📑 บันทึกเป็น PDF", use_container_width=True):
1224
+ try:
1225
+ pdf = create_story_pdf()
1226
+ st.download_button(
1227
+ "📥 ดาวน์โหลด PDF",
1228
+ data=pdf,
1229
+ file_name=f"story_{datetime.now().strftime('%Y%m%d')}.pdf",
1230
+ mime="application/pdf"
1231
+ )
1232
+ except Exception as e:
1233
+ logging.error(f"Error creating PDF: {str(e)}")
1234
+ st.error("เกิดข้อผิดพลาดในการสร้างไฟล์ PDF กรุณาลองใหม่อีกครั้ง")
1235
+
1236
+ with col2:
1237
+ # JSON Save
1238
+ if st.button("💾 บันทึกความก้าวหน้า", use_container_width=True):
1239
+ try:
1240
+ story_data = {
1241
+ 'timestamp': datetime.now().isoformat(),
1242
+ 'level': st.session_state.level,
1243
+ 'story': st.session_state.story,
1244
+ 'achievements': st.session_state.achievements,
1245
+ 'stats': convert_sets_to_lists(st.session_state.stats),
1246
+ 'points': st.session_state.points
1247
+ }
1248
+
1249
+ st.download_button(
1250
+ "📥 ดาวน์โหลดไฟล์บันทึก",
1251
+ data=json.dumps(story_data, ensure_ascii=False, indent=2),
1252
+ file_name=f"story_progress_{datetime.now().strftime('%Y%m%d')}.json",
1253
+ mime="application/json"
1254
+ )
1255
+ except Exception as e:
1256
+ logging.error(f"Error saving progress: {str(e)}")
1257
+ st.error("เกิดข้อผิดพลาดในการบันทึกความก้าวหน้า กรุณาลองใหม่อีกครั้ง")
1258
+
1259
+ def show_sidebar():
1260
+ """Display sidebar content"""
1261
+ st.sidebar.markdown("### ⚙️ การตั้งค่า")
1262
+
1263
+ # Progress Upload
1264
+ st.sidebar.markdown("#### 📂 โหลดความก้าวหน้า")
1265
+ uploaded_file = st.sidebar.file_uploader(
1266
+ "เลือกไฟล์ .json",
1267
+ type=['json'],
1268
+ help="เลือกไฟล์ความก้าวหน้าที่บันทึกไว้"
1269
+ )
1270
+ if uploaded_file:
1271
+ if st.sidebar.button("โหลดความก้าวหน้า", use_container_width=True):
1272
+ try:
1273
+ data = json.loads(uploaded_file.getvalue())
1274
+ load_progress(data)
1275
+ st.sidebar.success("โหลดความก้าวหน้าเรียบร้อย!")
1276
+ st.rerun()
1277
+ except Exception as e:
1278
+ logging.error(f"Error loading progress: {str(e)}")
1279
+ st.sidebar.error("เกิดข้อผิดพลาดในการโหลดไฟล์")
1280
+
1281
+ # Level Selection
1282
+ st.sidebar.markdown("#### 🎯 ระดับการเรียน")
1283
+ level = show_level_selection()
1284
+ st.session_state.level = level
1285
+
1286
+ # Theme Change Option
1287
+ if st.session_state.current_theme:
1288
+ st.sidebar.markdown("#### 🎨 ธีมเรื่องราว")
1289
+ if st.sidebar.button("🔄 เปลี่ยนธีม", use_container_width=True):
1290
+ st.session_state.current_theme = None
1291
+ st.session_state.theme_story_starter = None
1292
+ st.rerun()
1293
+
1294
+ # Reset Option
1295
+ st.sidebar.markdown("#### 🔄 รีเซ็ตเรื่องราว")
1296
+ if st.sidebar.button("เริ่มเรื่องใหม่", use_container_width=True):
1297
+ if st.session_state.story:
1298
+ if st.sidebar.checkbox("ยืนยันการเริ่มใหม่"):
1299
+ st.session_state.should_reset = True
1300
+ st.rerun()
1301
+ else:
1302
+ st.sidebar.info("ยังไม่มีเรื่องราวที่จะรีเซ็ต")
1303
+
1304
+ def show_story_input():
1305
+ """Display story input section"""
1306
+ st.markdown("""
1307
+ <div class="thai-eng">
1308
+ <div class="thai">✏️ ถึงตาคุณแล้ว</div>
1309
+ <div class="eng">Your Turn</div>
1310
+ </div>
1311
+ """, unsafe_allow_html=True)
1312
+
1313
+ # Input area
1314
+ text_input = st.text_area(
1315
+ "เขียนต่อจากเรื่องราว | Continue the story:",
1316
+ height=100,
1317
+ key="story_input_area",
1318
+ help="พิมพ์ประโยคภาษาอังกฤษเพื่อต่อเรื่อง",
1319
+ label_visibility="collapsed"
1320
+ )
1321
+
1322
+ # Submit button with character count
1323
+ col1, col2 = st.columns([3, 1])
1324
+ with col1:
1325
+ if st.button("📝 ส่งคำตอบ | Submit", use_container_width=True):
1326
+ if not text_input.strip():
1327
+ st.warning("กรุณาเขียนข้อความก่อนส่ง")
1328
+ return
1329
+
1330
+ try:
1331
+ with st.spinner("กำลังวิเคราะห์ประโยค..."):
1332
+ handle_story_submission(text_input.strip())
1333
+ except Exception as e:
1334
+ logging.error(f"Error submitting story: {str(e)}")
1335
+ st.error("เกิดข้อผิดพลาดในการส่งคำตอบ กรุณาลองใหม่อีกครั้ง")
1336
+
1337
+ with col2:
1338
+ char_count = len(text_input)
1339
+ st.markdown(f"""
1340
+ <div style="text-align: right; color: {'red' if char_count > 200 else '#666'};">
1341
+ {char_count}/200 ตัวอักษร
1342
+ </div>
1343
+ """, unsafe_allow_html=True)
1344
+
1345
+ def handle_story_submission(text: str):
1346
+ """Handle story submission and processing"""
1347
+ if not st.session_state.story:
1348
+ st.error("กรุณาเลือกธีมเรื่องราวก่อนเริ่มเขียน")
1349
+ return
1350
+
1351
+ try:
1352
+ # Get feedback
1353
+ feedback_data = provide_feedback(text, st.session_state.level)
1354
+ st.session_state.feedback = feedback_data
1355
+ is_correct = not feedback_data.get('has_errors', False)
1356
+
1357
+ # Add user's sentence
1358
+ st.session_state.story.append({
1359
+ "role": "You",
1360
+ "content": text,
1361
+ "is_corrected": False,
1362
+ "is_correct": is_correct,
1363
+ "timestamp": datetime.now().isoformat()
1364
+ })
1365
+
1366
+ # Update vocabulary
1367
+ words = set(text.lower().split())
1368
+ st.session_state.stats['vocabulary_used'].update(words)
1369
+
1370
+ # Update points and achievements
1371
+ update_points(is_correct)
1372
+ update_achievements()
1373
+
1374
+ # Generate AI continuation
1375
+ if not feedback_data.get('has_errors'):
1376
+ with st.spinner("กำลังต่อเรื่อง..."):
1377
+ ai_response = generate_story_continuation(text, st.session_state.level)
1378
+ st.session_state.story.append({
1379
+ "role": "AI",
1380
+ "content": ai_response,
1381
+ "timestamp": datetime.now().isoformat()
1382
+ })
1383
+
1384
+ # Update session stats
1385
+ update_session_stats()
1386
+
1387
+ # Clear input
1388
+ st.session_state.story_input_area = ""
1389
+
1390
+ except Exception as e:
1391
+ logging.error(f"Error in story submission: {str(e)}")
1392
+ raise
1393
+
1394
+ def show_main_interface():
1395
+ """Display main story interface"""
1396
+ col1, col2 = st.columns([3, 1])
1397
+
1398
+ with col1:
1399
+ # Story display
1400
+ st.markdown("""
1401
+ <div class="thai-eng">
1402
+ <div class="thai">📖 เรื่องราวของคุณ</div>
1403
+ <div class="eng">Your Story</div>
1404
+ </div>
1405
+ """, unsafe_allow_html=True)
1406
+
1407
+ show_story()
1408
+
1409
+ if st.session_state.story:
1410
+ show_story_input()
1411
+
1412
+ with col2:
1413
+ # Feedback section
1414
+ show_feedback_section()
1415
+
1416
+ # Writing tools
1417
+ show_writing_tools()
1418
+
1419
+ # Achievements
1420
+ with st.expander("🏆 ความสำเร็จ | Achievements"):
1421
+ show_achievements()
1422
+
1423
+ # Save options
1424
+ show_save_options()
1425
+
1426
+ # === 6. MAIN APPLICATION LOGIC ===
1427
+ def main():
1428
+ """Main application entry point"""
1429
+ try:
1430
+ # Initialize states
1431
+ init_session_state()
1432
+ init_theme_state()
1433
+
1434
+ # Add watermark
1435
+ st.markdown("""
1436
+ <div style='position: fixed; bottom: 10px; right: 10px; z-index: 1000;
1437
+ opacity: 0.7; font-size: 0.8em; color: #666;'>
1438
+ Powered by JoyStory AI
1439
+ </div>
1440
+ """, unsafe_allow_html=True)
1441
+
1442
+ # Show header
1443
+ st.markdown("# 📖 JoyStory")
1444
+ show_welcome_section()
1445
+ show_parent_guide()
1446
+
1447
+ # Sidebar
1448
+ with st.sidebar:
1449
+ show_sidebar()
1450
+
1451
+ # Session Status Check
1452
+ check_session_status()
1453
+
1454
+ # Main content area
1455
+ main_container = st.container()
1456
+ with main_container:
1457
+ if not st.session_state.current_theme:
1458
+ show_theme_selection()
1459
+ else:
1460
+ show_main_interface()
1461
+
1462
+ # Handle reset if needed
1463
+ if st.session_state.should_reset:
1464
+ reset_story()
1465
+
1466
+ # Auto-save progress periodically
1467
+ if st.session_state.story:
1468
+ auto_save_progress()
1469
+
1470
+ except Exception as e:
1471
+ handle_application_error(e)
1472
+
1473
+ def check_session_status():
1474
+ """Check and maintain session status"""
1475
+ try:
1476
+ # Update session duration
1477
+ if 'session_start' not in st.session_state:
1478
+ st.session_state.session_start = datetime.now()
1479
+
1480
+ # Check for session timeout (2 hours)
1481
+ session_duration = (datetime.now() - st.session_state.session_start).total_seconds()
1482
+ if session_duration > 7200: # 2 hours
1483
+ st.warning("เซสชันหมดอายุ กรุณาบันทึกความก้าวหน้าและรีเฟรชหน้าเว็บ")
1484
+
1485
+ # Check for inactivity (30 minutes)
1486
+ last_interaction = datetime.fromisoformat(st.session_state.last_interaction)
1487
+ inactivity_duration = (datetime.now() - last_interaction).total_seconds()
1488
+ if inactivity_duration > 1800: # 30 minutes
1489
+ st.info("ไม่มีกิจกรรมเป็นเวลานาน กรุณาบันทึกความก้าวหน้าเพื่อความปลอดภัย")
1490
+
1491
+ # Update stats if story exists
1492
+ if st.session_state.story:
1493
+ update_session_stats()
1494
+
1495
+ except Exception as e:
1496
+ logging.error(f"Error checking session status: {str(e)}")
1497
+
1498
+ def auto_save_progress():
1499
+ """Automatically save progress to session state"""
1500
+ try:
1501
+ current_progress = {
1502
+ 'timestamp': datetime.now().isoformat(),
1503
+ 'story': st.session_state.story,
1504
+ 'stats': st.session_state.stats,
1505
+ 'points': st.session_state.points,
1506
+ 'achievements': st.session_state.achievements
1507
+ }
1508
+
1509
+ # Save to session state
1510
+ if 'auto_save' not in st.session_state:
1511
+ st.session_state.auto_save = {}
1512
+
1513
+ st.session_state.auto_save = current_progress
1514
+
1515
+ # Add timestamp for last auto-save
1516
+ st.session_state.last_auto_save = datetime.now().isoformat()
1517
+
1518
+ except Exception as e:
1519
+ logging.error(f"Error in auto-save: {str(e)}")
1520
+
1521
+ def handle_application_error(error: Exception):
1522
+ """Handle application-wide errors"""
1523
+ logging.error(f"Application error: {str(error)}")
1524
+
1525
+ error_message = """
1526
+ <div style="
1527
+ background-color: #ffebee;
1528
+ padding: 20px;
1529
+ border-radius: 10px;
1530
+ border-left: 4px solid #c62828;
1531
+ margin: 20px 0;
1532
+ ">
1533
+ <h3 style="color: #c62828; margin: 0 0 10px 0;">
1534
+ ⚠️ เกิดข้อผิดพลาดในระบบ
1535
+ </h3>
1536
+ <p style="color: #333; margin: 0;">
1537
+ กรุณาลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบ
1538
+ </p>
1539
+ </div>
1540
+ """
1541
+
1542
+ st.markdown(error_message, unsafe_allow_html=True)
1543
+
1544
+ # Show technical details in expander
1545
+ with st.expander("รายละเอียดข้อผิดพลาด (สำหรับผู้ดูแลระบบ)"):
1546
+ st.code(f"""
1547
+ Error Type: {type(error).__name__}
1548
+ Error Message: {str(error)}
1549
+ Timestamp: {datetime.now().isoformat()}
1550
+ """)
1551
+
1552
+ def show_debug_info():
1553
+ """Show debug information (development only)"""
1554
+ if st.session_state.get('debug_mode'):
1555
+ with st.expander("🔧 Debug Information"):
1556
+ st.json({
1557
+ 'session_state': {
1558
+ key: str(value) if isinstance(value, (set, datetime)) else value
1559
+ for key, value in st.session_state.items()
1560
+ if key not in ['client', '_client']
1561
+ }
1562
+ })
1563
+
1564
+ # Add CSS for loading animation
1565
+ st.markdown("""
1566
+ <style>
1567
+ @keyframes pulse {
1568
+ 0% { opacity: 0.6; }
1569
+ 50% { opacity: 1; }
1570
+ 100% { opacity: 0.6; }
1571
+ }
1572
+
1573
+ .loading-animation {
1574
+ animation: pulse 1.5s infinite;
1575
+ background-color: #f0f2f5;
1576
+ border-radius: 8px;
1577
+ padding: 20px;
1578
+ text-align: center;
1579
+ margin: 20px 0;
1580
+ }
1581
+
1582
+ /* Custom Scrollbar */
1583
+ ::-webkit-scrollbar {
1584
+ width: 8px;
1585
+ height: 8px;
1586
+ }
1587
+
1588
+ ::-webkit-scrollbar-track {
1589
+ background: #f1f1f1;
1590
+ border-radius: 4px;
1591
+ }
1592
+
1593
+ ::-webkit-scrollbar-thumb {
1594
+ background: #888;
1595
+ border-radius: 4px;
1596
+ }
1597
+
1598
+ ::-webkit-scrollbar-thumb:hover {
1599
+ background: #666;
1600
+ }
1601
+
1602
+ /* Toast Notifications */
1603
+ .toast-notification {
1604
+ position: fixed;
1605
+ bottom: 20px;
1606
+ right: 20px;
1607
+ padding: 15px 25px;
1608
+ background-color: #333;
1609
+ color: white;
1610
+ border-radius: 8px;
1611
+ z-index: 1000;
1612
+ animation: slideIn 0.3s ease-out;
1613
+ }
1614
+
1615
+ @keyframes slideIn {
1616
+ from { transform: translateX(100%); }
1617
+ to { transform: translateX(0); }
1618
+ }
1619
+
1620
+ /* Progress Indicator */
1621
+ .progress-bar {
1622
+ width: 100%;
1623
+ height: 4px;
1624
+ background-color: #e0e0e0;
1625
+ border-radius: 2px;
1626
+ overflow: hidden;
1627
+ }
1628
+
1629
+ .progress-bar-fill {
1630
+ height: 100%;
1631
+ background-color: #1e88e5;
1632
+ transition: width 0.3s ease;
1633
+ }
1634
+ </style>
1635
+ """, unsafe_allow_html=True)
1636
+
1637
+ if __name__ == "__main__":
1638
+ try:
1639
+ main()
1640
+ except Exception as e:
1641
+ handle_application_error(e)