-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.py
596 lines (521 loc) · 24.4 KB
/
app.py
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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
import streamlit as st
import random
import json
from dotenv import load_dotenv
import os
import google.generativeai as genai
from dataclasses import dataclass
from typing import List, Dict, Any
import datetime
# Configuration class
@dataclass
class Config:
MODEL_NAME: str = "gemini-1.5-pro-latest"
MAX_CONTEXT_LENGTH: int = 1048576
MAX_OUTPUT_TOKENS: int = 8192
ROUNDS_PER_GAME: int = 5
# Load environment variables
load_dotenv(override=True)
# Configure Gemini
genai.configure(api_key=os.getenv('GEMINI_API_KEY'))
# Initialize Gemini model
model = genai.GenerativeModel(
Config.MODEL_NAME,
generation_config={
'temperature': 0.7,
'top_p': 0.8,
'top_k': 40,
'max_output_tokens': Config.MAX_OUTPUT_TOKENS
}
)
# Page configuration
st.set_page_config(
page_title="SWA Crew Quest: Trivia & Training",
page_icon="✈️",
layout="wide"
)
# Custom CSS
st.markdown("""
<style>
.scenario-box {
background-color: #f0f2f6;
padding: 20px;
border-radius: 10px;
margin: 10px 0;
}
.game-stats {
background-color: #e3f2fd;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.chip {
padding: 5px 15px;
border-radius: 16px;
display: inline-block;
margin: 2px;
font-size: 14px;
}
.chip-correct {
background-color: #4caf50;
color: white;
}
.chip-incorrect {
background-color: #f44336;
color: white;
}
.chip-context {
background-color: #2196f3;
color: white;
}
.chip-difficulty {
background-color: #ff9800;
color: white;
}
.question-summary {
background-color: #ffffff;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
border: 1px solid #e0e0e0;
}
</style>
""", unsafe_allow_html=True)
# Fallback scenarios in case of API issues
FALLBACK_SCENARIOS = [
{
"scenario": "During boarding, a passenger is struggling with an oversized bag while others wait",
"context": "Boarding",
"difficulty": "Easy",
"points": 5,
"options": [
{"text": "Offer to gate-check the bag for free", "is_correct": True},
{"text": "Let them keep trying while the line builds up", "is_correct": False},
{"text": "Tell them they have to check it at the counter", "is_correct": False}
],
"explanation": "Offering free gate-check keeps boarding moving and maintains good customer service."
},
# Add more fallback scenarios here...
]
class GameManager:
def __init__(self):
self.initialize_session_state()
def initialize_session_state(self):
"""Initialize all session state variables"""
if 'game_active' not in st.session_state:
st.session_state.game_active = False
if 'current_round' not in st.session_state:
st.session_state.current_round = 0
if 'total_score' not in st.session_state:
st.session_state.total_score = 0
if 'player_name' not in st.session_state:
st.session_state.player_name = ""
if 'game_history' not in st.session_state:
st.session_state.game_history = []
if 'leaderboard' not in st.session_state:
st.session_state.leaderboard = []
if 'current_scenario' not in st.session_state:
st.session_state.current_scenario = None
if 'showing_answer' not in st.session_state:
st.session_state.showing_answer = False
if 'player_role' not in st.session_state:
st.session_state.player_role = "Any" # Default to "Any" if no role selected
if 'show_about' not in st.session_state:
st.session_state.show_about = False
if 'topic_categories' not in st.session_state:
st.session_state.topic_categories = {
'customer_service': [],
'operations': [],
'culture': [],
'history': [],
'technical': [],
'fun_moments': [],
'problem_solving': [],
'teamwork': [],
'leadership': [],
'innovation': []
}
def generate_scenario(self):
"""Generate a new unique scenario or airline trivia question using Gemini"""
is_trivia = random.choice([True, False])
# Get selected role from session state
selected_role = st.session_state.player_role
# Add role-specific context to the prompt if a specific role was selected
role_context = ""
if selected_role != "Any Role":
role_context = f"""
Focus on scenarios and questions specifically relevant to a {selected_role}.
Make sure the situations and questions are realistic and appropriate for this role.
Include role-specific terminology and procedures when applicable.
"""
# Get least used categories
used_counts = {cat: len(topics) for cat, topics in st.session_state.topic_categories.items()}
min_count = min(used_counts.values())
available_categories = [cat for cat, count in used_counts.items() if count == min_count]
selected_category = random.choice(available_categories)
if is_trivia:
prompt = f"""
Generate an interesting aviation trivia question for category: {selected_category}
{role_context}
Focus areas for this category:
- For customer_service: Unique passenger interactions, creative solutions
- For operations: Airport procedures, flight planning, scheduling
- For culture: Airline traditions, company values, celebrations
- For history: Airline milestones, industry developments
- For technical: Aircraft systems, aviation technology
- For fun_moments: Memorable flights, special events
- For problem_solving: Creative solutions, quick thinking
- For teamwork: Crew coordination, ground cooperation
- For leadership: Captain decisions, crew management
- For innovation: New procedures, industry firsts
Requirements:
1. Make it engaging, unique, and educational
2. Provide three distinct answer options
3. Include surprising facts in the explanation
4. Add three fascinating aviation fun facts
Return as JSON:
{{
"scenario": "Your trivia question",
"context": "Category context",
"category": "{selected_category}",
"difficulty": "Easy/Medium/Hard",
"points": number 5-15,
"options": [
{{"text": "option 1", "is_correct": true/false}},
{{"text": "option 2", "is_correct": true/false}},
{{"text": "option 3", "is_correct": true/false}}
],
"explanation": "Detailed explanation",
"fun_facts": [
"fact 1",
"fact 2",
"fact 3"
]
}}
"""
else:
prompt = f"""
Generate a unique crew scenario for category: {selected_category}
{role_context}
Focus areas for this category:
- For customer_service: Unique passenger situations, special requests
- For operations: Unusual flight situations, ground operations
- For culture: Team building, company values in action
- For history: Using experience in current situations
- For technical: Handling equipment, system operations
- For fun_moments: Creating special memories, celebrations
- For problem_solving: Unexpected challenges, creative solutions
- For teamwork: Crew cooperation, department coordination
- For leadership: Guiding others, making decisions
- For innovation: Trying new approaches, improvements
Requirements:
1. Create an engaging scenario not previously used
2. Make it realistic but interesting
3. Provide three distinct response options
4. Include practical learning in explanation
5. Add three fascinating aviation fun facts
Return as JSON:
{{
"scenario": "Your unique scenario",
"context": "Category context",
"category": "{selected_category}",
"difficulty": "Easy/Medium/Hard",
"points": number 5-15,
"options": [
{{"text": "option 1", "is_correct": true/false}},
{{"text": "option 2", "is_correct": true/false}},
{{"text": "option 3", "is_correct": true/false}}
],
"explanation": "Detailed explanation",
"fun_facts": [
"fact 1",
"fact 2",
"fact 3"
]
}}
"""
max_retries = 5
for attempt in range(max_retries):
try:
response = model.generate_content(prompt)
scenario_str = response.text.strip()
scenario_str = scenario_str.replace("```json", "").replace("```", "").strip()
scenario = json.loads(scenario_str)
# Add the scenario to its category
category = scenario.get('category', selected_category)
st.session_state.topic_categories[category].append(scenario['scenario'])
# Randomly shuffle options
options = scenario['options']
random.shuffle(options)
scenario['options'] = options
# Add type flag to scenario
scenario['is_trivia'] = is_trivia
return scenario
except Exception as e:
if attempt == max_retries - 1:
# If all attempts fail, create a basic scenario from templates
return self.generate_fallback_scenario(selected_category, is_trivia)
continue
return self.generate_fallback_scenario(selected_category, is_trivia)
def generate_fallback_scenario(self, category, is_trivia):
"""Generate a basic scenario based on templates if API fails"""
templates = {
'customer_service': {
'scenario': f"A passenger requests a unique accommodation during {random.choice(['boarding', 'the flight', 'deplaning'])}",
'options': [
{"text": "Address their needs with a creative solution", "is_correct": True},
{"text": "Refer them to another department", "is_correct": False},
{"text": "Explain why it's not possible", "is_correct": False}
]
},
'operations': {
'scenario': f"An unexpected {random.choice(['weather change', 'schedule adjustment', 'equipment issue'])} requires quick thinking",
'options': [
{"text": "Implement efficient backup plan", "is_correct": True},
{"text": "Continue with original plan", "is_correct": False},
{"text": "Wait for further instructions", "is_correct": False}
]
},
'culture': {
'scenario': f"An opportunity arises to demonstrate SWA's {random.choice(['spirit', 'values', 'culture'])} during a flight",
'options': [
{"text": "Create a memorable moment", "is_correct": True},
{"text": "Focus on routine tasks", "is_correct": False},
{"text": "Let someone else handle it", "is_correct": False}
]
}
}
# If category not in templates, use random template
template = templates.get(category, random.choice(list(templates.values())))
scenario = {
'scenario': template['scenario'],
'context': category.replace('_', ' ').title(),
'category': category,
'difficulty': 'Medium',
'points': 10,
'options': template['options'],
'explanation': "The best approach is to be proactive and solution-oriented while maintaining Southwest's values.",
'fun_facts': [
"Southwest Airlines was the first airline to offer profit-sharing to employees in 1973",
"The average flight attendant walks about 5 miles during each flight",
"Southwest's first flight took off from Dallas Love Field in 1971"
],
'is_trivia': is_trivia
}
return scenario
def display_scenario(self, scenario):
"""Display the current scenario or trivia with enhanced visuals"""
is_trivia = scenario.get('is_trivia', False)
icon = "🎯" if is_trivia else "✈️"
title = "Aviation Trivia" if is_trivia else f"Scenario {st.session_state.current_round}"
category = scenario.get('category', '').replace('_', ' ').title()
st.markdown(
f"""
<div class="scenario-box">
<span class="chip chip-context">{scenario['context']}</span>
<span class="chip chip-difficulty">{scenario['difficulty']}</span>
<h3>{icon} {title}</h3>
<h4>Category: {category}</h4>
<p>{scenario['scenario']}</p>
<p><strong>Points possible: {scenario['points']}</strong></p>
</div>
""",
unsafe_allow_html=True
)
options = [option['text'] for option in scenario['options']]
choice = st.radio("Select your answer:", options)
return choice
def process_answer(self, scenario, choice):
"""Process the answer and display feedback with fun facts that are also true and accurate to SWA or the aviation industry"""
for option in scenario['options']:
if option['text'] == choice:
is_correct = option['is_correct']
points = scenario['points'] if is_correct else 0
if is_correct:
st.success(f"✅ Correct! You earned {points} points!", icon="🎯")
else:
correct_answer = next(opt['text'] for opt in scenario['options'] if opt['is_correct'])
st.error("❌ Not quite right.", icon="⚠️")
st.warning(f"The correct answer was: {correct_answer}")
st.info(f"📝 Explanation: {scenario['explanation']}")
with st.expander("🎨 Fun Aviation Facts"):
for i, fact in enumerate(scenario['fun_facts'], 1):
st.markdown(f"**Fact {i}:** {fact}")
# Add delay to ensure user can read feedback
st.empty().markdown("---")
return points, is_correct, scenario['explanation']
return 0, False, "Error processing answer"
def display_game_stats(self):
"""Display current game statistics"""
role_display = f"Role: {st.session_state.player_role} | " if st.session_state.player_role != "Any Role" else ""
st.markdown(
f"""
<div class="game-stats">
<h3>✈️ Flight Log</h3>
<p>
Round: {st.session_state.current_round}/{Config.ROUNDS_PER_GAME} |
Score: {st.session_state.total_score} points |
{role_display}
Player: {st.session_state.player_name}
</p>
</div>
""",
unsafe_allow_html=True
)
def display_game_summary(self):
"""Display end-game summary with enhanced visuals"""
st.markdown("## Game Summary")
st.markdown(f"**Final Score: {st.session_state.total_score} points**")
for idx, round_data in enumerate(st.session_state.game_history, 1):
with st.expander(f"Round {idx} - {round_data['context']}"):
st.markdown(
f"""
<div class="question-summary">
<span class="chip chip-context">{round_data['context']}</span>
<span class="chip chip-difficulty">{round_data['difficulty']}</span>
<p><strong>Scenario:</strong> {round_data['scenario']}</p>
<p><strong>Your Answer:</strong> {round_data['player_choice']}</p>
<p><strong>Correct Answer:</strong> {round_data['correct_answer']}</p>
<p><strong>Points Earned:</strong> {round_data['points']}/{round_data['possible_points']}</p>
<p><strong>Explanation:</strong> {round_data['explanation']}</p>
</div>
""",
unsafe_allow_html=True
)
def update_leaderboard(self):
"""Update the leaderboard with current game results"""
st.session_state.leaderboard.append({
"name": st.session_state.player_name,
"score": st.session_state.total_score,
"date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
})
st.session_state.leaderboard.sort(key=lambda x: x["score"], reverse=True)
st.session_state.leaderboard = st.session_state.leaderboard[:10]
def main():
game = GameManager()
st.title("✈️ SWA Crew Quest: Trivia & Training")
# Sidebar
with st.sidebar:
st.markdown("### ✈️ Freedom One: The First SWA 737-800")
st.markdown("## Source: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Freedom_one.png)")
st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Freedom_one.png/320px-Freedom_one.png", width=200)
st.markdown("### 🏆 Top Performers")
for idx, entry in enumerate(st.session_state.leaderboard[:5], 1):
st.write(f"{idx}. {entry['name']}: {entry['score']} pts ({entry['date']})")
st.markdown("---") # Adds a separator line
st.markdown("### 🌟 ✈️ About This App ❤️ 🎯")
# Toggle button with dynamic text
button_text = "✨ Hide About SWA Crew Quest ✨" if st.session_state.show_about else "✨ Learn More About SWA Crew Quest ✨"
if st.button(button_text, key="about_button"):
st.markdown("""
<style>
.about-box {
background-color: #f0f2f6;
padding: 20px;
border-radius: 10px;
border: 2px solid #1e88e5;
margin: 10px 0;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.developer-note {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #1e88e5;
}
</style>
<div class="about-box">
<p>Welcome to SWA Crew Quest: Trivia & Training—a heartfelt tribute to Southwest Airlines' dedicated team members who make every flight special.</p>
<p>Through engaging scenarios and trivia, we celebrate the professionalism, humor, and hospitality that helps SWA safely transport over 137 million passengers each year.</p>
<p>Thank you for your unwavering dedication to excellence and for making every journey memorable.</p>
<p>👨👩👧👦 ✈️ Your Happy and Thankful SWA Passengers 🙏 💝</p>
<div class="developer-note">
<p>✨ Developed by Lindsay Hiebert, a Generative AI expert creating outcomes that focus on the health, happiness, and growth of people—just like every member of the SWA team.</p>
<p>💡 Connect with me on <a href="https://www.linkedin.com/in/lindsayhiebert/" target="_blank" style="color: #1e88e5;">LinkedIn</a> for feedback or to learn more about AI solutions that make a difference.</p>
</div>
</div>
""", unsafe_allow_html=True)
st.session_state.show_about = not st.session_state.show_about
if st.session_state.game_active:
game.display_game_stats()
if not st.session_state.game_active:
st.markdown("""
### Welcome to a New Day of SWA Experience!
Test your skills in handling real-world situations across 5 unique and challenging scenarios.
Show us you've got the Southwest Spirit! ✨
""")
name = st.text_input("Enter your name:", key="name_input")
# Add this new section for role selection
cols = st.columns([2, 1]) # Create two columns for layout
with cols[0]:
role = st.selectbox(
"Choose your role (Optional):",
[
"Any Role",
"Flight Attendant",
"Pilot",
"Ground Operations",
"Customer Service Agent",
"Operations Agent"
],
index=0, # Default to "Any Role"
help="Select your SWA role to get role-specific scenarios!"
)
with cols[1]:
if st.button("Start Your Challenge! ✈️"):
if name.strip():
st.session_state.player_name = name
st.session_state.player_role = role
st.session_state.game_active = True
st.session_state.current_round = 1
st.session_state.total_score = 0
st.session_state.game_history = []
st.rerun()
else:
st.warning("Please enter your name to begin!")
elif st.session_state.current_round <= Config.ROUNDS_PER_GAME:
if st.session_state.current_scenario is None:
st.session_state.current_scenario = game.generate_scenario()
choice = game.display_scenario(st.session_state.current_scenario)
if st.button("Submit Answer"):
points, is_correct, explanation = game.process_answer(
st.session_state.current_scenario, choice
)
st.session_state.total_score += points
# Record round history
st.session_state.game_history.append({
"round": st.session_state.current_round,
"context": st.session_state.current_scenario['context'],
"difficulty": st.session_state.current_scenario['difficulty'],
"scenario": st.session_state.current_scenario['scenario'],
"player_choice": choice,
"correct_answer": next(opt['text'] for opt in st.session_state.current_scenario['options'] if opt['is_correct']),
"points": points,
"possible_points": st.session_state.current_scenario['points'],
"explanation": explanation
})
# Display immediate feedback
if is_correct:
st.success(f"Correct! You earned {points} points!")
else:
st.error("Not quite right. No points awarded.")
st.info(f"Explanation: {explanation}")
st.session_state.current_round += 1
st.session_state.current_scenario = None
if st.session_state.current_round <= Config.ROUNDS_PER_GAME:
if st.button("Next Scenario"):
st.rerun()
else:
game.update_leaderboard()
st.rerun()
else:
st.balloons()
game.display_game_summary()
if st.button("Start New Game"):
st.session_state.game_active = False
st.rerun()
if __name__ == "__main__":
main()