vighnesh-shetty-vs commited on
Commit
bcca1cb
·
1 Parent(s): f532a5e

Add updated files

Browse files
Files changed (1) hide show
  1. app.py +146 -149
app.py CHANGED
@@ -4,13 +4,12 @@ import random
4
  from gtts import gTTS
5
  import os
6
 
7
- # Import your local modules (Ensure game_data.py, dialogues.py, alternatives.py exist in the same folder)
8
  from game_data import calculate_impact
9
  from dialogues import get_dialogue
10
  from alternatives import ALTERNATIVES
11
 
12
  # --- 1. Load AI Models ---
13
- print("Loading classification model (facebook/bart-large-mnli)...")
14
  classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
15
 
16
  CATEGORIES = [
@@ -23,240 +22,238 @@ CATEGORIES = [
23
  # --- 2. Helper Functions & UI Generators ---
24
 
25
  def generate_audio(text):
26
- """Generates instant TTS audio using gTTS to prevent CPU timeouts."""
27
  tts = gTTS(text, lang='en')
28
  filepath = "temp_drip_voice.mp3"
29
  tts.save(filepath)
30
  return filepath
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  def get_drip_visuals(water_level):
33
- """Generates the CSS/HTML for Drip based on dehydration."""
34
  if water_level > 70:
35
- emoji, size, animation = "💧", "80px", "bounce 2s infinite"
36
- status = "Plump & Hydrated"
37
  elif 30 <= water_level <= 70:
38
- emoji, size, animation = "💧", "60px", "none"
39
- status = "Shrinking & Tired"
40
  elif water_level > 0:
41
- emoji, size, animation = "🏜️", "40px", "none"
42
- status = "Cracked & Raspy"
43
  else:
44
- emoji, size, animation = "💨", "30px", "none"
45
- status = "Evaporated!"
46
 
47
  return f"""
48
- <div style="text-align: center; margin-bottom: 20px;">
49
- <div style="font-size: {size}; animation: {animation}; transition: all 0.5s;">{emoji}</div>
50
- <h3 style="margin: 0;">Drip Status: {status}</h3>
51
  </div>
52
  """
53
 
54
  def get_impact_cards(impact, category):
55
- """Generates side-by-side comparison cards."""
56
- # Relatable translations
57
- water_trans = f"{impact['water']}L is like leaving a tap running for {int(impact['water']*12)} seconds."
58
- if impact['water'] >= 5:
59
- water_trans = f"{impact['water']}L = A whole day's drinking water for a family."
60
-
61
- energy_trans = f"{impact['energy']}kWh could charge your phone {int(impact['energy']*100)} times."
62
- if impact['energy'] >= 0.5:
63
- energy_trans = f"{impact['energy']}kWh = Powering a fridge for several hours."
64
-
65
  return f"""
66
- <div style="display: flex; gap: 20px; justify-content: center; margin-top: 20px;">
67
- <div style="flex: 1; padding: 15px; background: #ffebee; border-left: 5px solid #f44336; border-radius: 5px;">
68
- <h3 style="color: #c62828; margin-top: 0;">LLM Impact (Massive Servers)</h3>
69
- <p><strong>Water Wasted:</strong> {impact['water']}L</p>
70
- <p><strong>Energy Wasted:</strong> {impact['energy']}kWh</p>
71
- <p><em>{water_trans}</em></p>
72
- <p><em>{energy_trans}</em></p>
73
  </div>
74
- <div style="flex: 1; padding: 15px; background: #e8f5e9; border-left: 5px solid #4caf50; border-radius: 5px;">
75
- <h3 style="color: #2e7d32; margin-top: 0;">Eco-Alternative (Local/Search)</h3>
76
- <p><strong>Water Wasted:</strong> ~0.0L</p>
77
- <p><strong>Energy Wasted:</strong> ~0.001kWh</p>
78
- <p><em>Save the planet, use appropriate tools!</em></p>
79
  </div>
80
  </div>
81
  """
82
 
83
- def generate_report(state):
84
- """Generates the final game over report."""
85
- badges = []
86
- if state['water'] > 50: badges.append("🌊 Water Warrior")
87
- if state['eco_score'] >= 80: badges.append("🌿 Conscious Querier")
88
- if not badges: badges.append("🔥 Resource Drainer")
89
-
90
- return f"""
91
- <div style="background: #2c3e50; color: white; padding: 30px; border-radius: 10px; text-align: center;">
92
- <h1>Game Over! Here is your AI Audit.</h1>
93
- <h2>Final Score: {state['points']} Points</h2>
94
- <p>You asked {state['queries']} queries.</p>
95
- <p><strong>Total Water Drained:</strong> {100 - state['water']:.2f}L</p>
96
- <p><strong>Total Energy Consumed:</strong> {state['energy']:.3f}kWh</p>
97
- <p><strong>Eco-Score:</strong> {state['eco_score']:.1f}%</p>
98
- <h3>Badges Earned:</h3>
99
- <h2>{' | '.join(badges)}</h2>
100
- <p style="margin-top: 20px; font-size: 0.9em;"><em>Tip: Match the tool to the task. Don't use a supercomputer to ask for a recipe! Review the UNESCO guidelines on proportional AI usage.</em></p>
101
- </div>
102
- """
103
 
104
  # --- 3. Core Game Logic ---
105
 
106
  def process_query(user_input, state):
107
  if state["game_over"] or not user_input.strip():
108
- return state, gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(visible=False), gr.update(visible=False)
109
 
110
- # 1. Classification & Impact Calculation
111
  result = classifier(user_input, CATEGORIES)
112
  category = result['labels'][0]
113
  confidence = result['scores'][0]
114
 
115
  impact = calculate_impact(category, confidence, user_input)
116
 
117
- # 2. Update Basic State
118
  state["queries"] += 1
119
  state["water"] = max(0, state["water"] - impact["water"])
120
  state["energy"] += impact["energy"]
121
- state["history"].append(category)
122
-
123
- # 3. Category Specific Logic (Points & Alternatives)
124
  show_alts = False
125
- alt_choices = []
126
- correct_alt = None
127
 
128
  if category == "complex research query":
129
- # Justified use: +50 points, no alternatives needed
130
  state["points"] += 50
131
- dialogue = "Finally, a real question! This is what my servers were built for. Draining, but worth it."
132
  else:
133
- # Unjustified/Avoidable: Fetch alternatives
134
  show_alts = True
135
- alt_choices = ALTERNATIVES.get(category, ["Google", "Wikipedia", "Book"])
136
- correct_alt = alt_choices[0] # For MVP, the first item in the list is the "best" one
137
- random.shuffle(alt_choices) # Shuffle so it's not always button 1
138
- state["current_best_alt"] = correct_alt
139
-
140
- # Get sarcastic Drip dialogue based on water level
141
  dialogue = get_dialogue(state["water"], category)
142
 
143
- # 4. Generate outputs
144
  drip_ui = get_drip_visuals(state["water"])
145
  impact_ui = get_impact_cards(impact, category)
146
  audio_path = generate_audio(dialogue)
 
147
 
148
- # Check Win/Loss
149
  if state["points"] >= 500 or state["water"] <= 0:
150
  state["game_over"] = True
151
- return (state, drip_ui, gr.update(value=state["water"]), gr.update(value=f"{state['energy']:.3f} kWh"),
152
- generate_report(state), gr.update(visible=False), audio_path, gr.update(visible=False), gr.update(visible=False))
153
-
154
- # Return updated UI components
155
- if show_alts:
156
- return (state, drip_ui, gr.update(value=state["water"]), gr.update(value=f"{state['energy']:.3f} kWh"),
157
- impact_ui, gr.update(value=f"**{category.upper()}** Detected! {dialogue}"), audio_path,
158
- gr.update(visible=True), gr.update(value=alt_choices[0]), gr.update(value=alt_choices[1]), gr.update(value=alt_choices[2]))
159
- else:
160
- return (state, drip_ui, gr.update(value=state["water"]), gr.update(value=f"{state['energy']:.3f} kWh"),
161
- impact_ui, gr.update(value=f"**{category.upper()}** Detected! {dialogue}"), audio_path,
162
- gr.update(visible=False), gr.update(), gr.update(), gr.update())
163
 
 
 
164
 
165
- def select_alternative(choice, state):
166
- """Handles logic when a user clicks an Eco-Alternative button."""
167
  if state["game_over"] or not state.get("current_best_alt"):
168
- return state, gr.update()
169
 
170
- msg = ""
171
- if choice == state["current_best_alt"]:
 
 
172
  state["points"] += 10
173
- state["eco_choices"] += 1
174
- msg = "✅ **Great choice!** You earned +10 Eco Points for picking the right tool for the job."
175
  else:
176
- msg = " **Not quite.** That works, but there was a better eco-friendly option. Still better than an LLM though!"
177
 
178
- # Update Eco-Score
179
- state["eco_score"] = (state["eco_choices"] / state["queries"]) * 100 if state["queries"] > 0 else 0
180
 
181
- # Hide alternatives after selection
182
- return state, gr.update(value=msg), gr.update(visible=False)
183
 
184
-
185
- # --- 4. Gradio Interface Layout ---
186
 
187
  custom_css = """
188
- .gradio-container { max-width: 800px !important; margin: auto; }
189
- .progress-bar-container { background: #e0e0e0; border-radius: 10px; height: 30px; width: 100%; overflow: hidden; margin-top: 5px; }
190
- .progress-bar { background: #2196f3; height: 100%; transition: width 0.5s ease-in-out; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  """
192
 
193
  with gr.Blocks(css=custom_css, title="EcoQueryQuest") as demo:
194
-
195
- # Initialize Game State
196
  game_state = gr.State({
197
  "water": 100.0, "energy": 0.0, "points": 0, "queries": 0,
198
- "eco_choices": 0, "eco_score": 0.0, "history": [], "game_over": False,
199
- "current_best_alt": None
200
  })
201
 
202
- gr.Markdown("# 🌍 EcoQueryQuest")
203
- gr.Markdown("Type your query below. Watch Drip (💧) react to the environmental cost of your prompt! Reach 500 points to win, but don't run out of water.")
 
 
 
204
 
205
- # TOP SECTION: Visuals & Trackers
206
  with gr.Row():
207
- with gr.Column(scale=1):
 
208
  drip_html = gr.HTML(get_drip_visuals(100))
209
- with gr.Column(scale=2):
210
- gr.Markdown("### 🚰 Water Tank (100L Limit)")
211
- water_slider = gr.Slider(minimum=0, maximum=100, value=100, interactive=False, show_label=False)
212
- gr.Markdown("### ⚡ Energy Wasted")
213
- energy_text = gr.Textbox(value="0.000 kWh", interactive=False, show_label=False)
214
- gr.Markdown("### 🏆 Points")
215
- points_text = gr.Textbox(value="0", interactive=False, show_label=False)
216
-
217
- # MIDDLE SECTION: Interaction
218
- with gr.Group() as interaction_group:
219
- user_input = gr.Textbox(label="Ask an AI a question...", placeholder="e.g., What is 25 * 42? or Write me a poem about dogs.")
220
- submit_btn = gr.Button("Send Query", variant="primary")
221
 
222
- main_display_html = gr.HTML()
223
- drip_dialogue_text = gr.Markdown()
224
- drip_audio = gr.Audio(label="Drip's Reaction", autoplay=True, interactive=False)
 
 
 
 
 
225
 
226
- # BOTTOM SECTION: Alternatives (Hidden by default)
227
- with gr.Group(visible=False) as alternatives_group:
228
- gr.Markdown("### Drip says: 'Choose a better alternative to save me!'")
229
  with gr.Row():
230
- alt_btn_1 = gr.Button("Alt 1")
231
- alt_btn_2 = gr.Button("Alt 2")
232
- alt_btn_3 = gr.Button("Alt 3")
233
- alt_feedback = gr.Markdown()
234
 
235
  # EVENT WIRING
236
-
237
- # 1. Submit Query
238
  submit_btn.click(
239
  fn=process_query,
240
  inputs=[user_input, game_state],
241
- outputs=[game_state, drip_html, water_slider, energy_text, main_display_html, drip_dialogue_text, drip_audio, alternatives_group, alt_btn_1, alt_btn_2, alt_btn_3]
242
- ).then(
243
- fn=lambda s: gr.update(value=str(s["points"])), # Update points UI after processing
244
- inputs=[game_state],
245
- outputs=[points_text]
246
  )
247
 
248
- # 2. Click Alternative Buttons
249
  for btn in [alt_btn_1, alt_btn_2, alt_btn_3]:
250
  btn.click(
251
  fn=select_alternative,
252
  inputs=[btn, game_state],
253
- outputs=[game_state, alt_feedback, alternatives_group]
254
- ).then(
255
- fn=lambda s: gr.update(value=str(s["points"])), # Update points UI after clicking alternative
256
- inputs=[game_state],
257
- outputs=[points_text]
258
  )
259
 
260
- # Launch the app
261
  if __name__ == "__main__":
262
  demo.launch()
 
4
  from gtts import gTTS
5
  import os
6
 
 
7
  from game_data import calculate_impact
8
  from dialogues import get_dialogue
9
  from alternatives import ALTERNATIVES
10
 
11
  # --- 1. Load AI Models ---
12
+ print("Loading classification model...")
13
  classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
14
 
15
  CATEGORIES = [
 
22
  # --- 2. Helper Functions & UI Generators ---
23
 
24
  def generate_audio(text):
 
25
  tts = gTTS(text, lang='en')
26
  filepath = "temp_drip_voice.mp3"
27
  tts.save(filepath)
28
  return filepath
29
 
30
+ def get_stats_html(water, energy, points):
31
+ """Generates the glowing dashboard gauges for the top row."""
32
+ return f"""
33
+ <div class="stats-container">
34
+ <div class="stat-card water-card">
35
+ <div class="stat-label">WATER TANK</div>
36
+ <div class="stat-value">{water:.1f}L</div>
37
+ <div class="progress-bar-bg">
38
+ <div class="progress-bar-fill" style="width: {water}%;"></div>
39
+ </div>
40
+ </div>
41
+ <div class="stat-card energy-card">
42
+ <div class="stat-label">ENERGY WASTED</div>
43
+ <div class="stat-value">{energy:.3f} <span style="font-size: 0.5em;">kWh</span></div>
44
+ </div>
45
+ <div class="stat-card points-card">
46
+ <div class="stat-label">SCORE</div>
47
+ <div class="stat-value">{points}</div>
48
+ </div>
49
+ </div>
50
+ """
51
+
52
  def get_drip_visuals(water_level):
53
+ """Generates Drip with a glowing aura based on health."""
54
  if water_level > 70:
55
+ emoji, size, anim, glow, status = "💧", "120px", "float 3s ease-in-out infinite", "rgba(56,189,248,0.6)", "Plump & Hydrated"
 
56
  elif 30 <= water_level <= 70:
57
+ emoji, size, anim, glow, status = "💧", "90px", "none", "rgba(56,189,248,0.3)", "Shrinking & Tired"
 
58
  elif water_level > 0:
59
+ emoji, size, anim, glow, status = "🏜️", "70px", "none", "rgba(239,68,68,0.4)", "Cracked & Raspy"
 
60
  else:
61
+ emoji, size, anim, glow, status = "💨", "50px", "none", "transparent", "Evaporated!"
 
62
 
63
  return f"""
64
+ <div class="drip-character" style="box-shadow: 0 0 40px {glow};">
65
+ <div style="font-size: {size}; animation: {anim}; transition: all 0.5s ease;">{emoji}</div>
66
+ <h3 style="margin: 10px 0 0 0; color: #e2e8f0; font-weight: 400; font-size: 1rem;">{status}</h3>
67
  </div>
68
  """
69
 
70
  def get_impact_cards(impact, category):
71
+ """Generates elegant side-by-side impact cards."""
 
 
 
 
 
 
 
 
 
72
  return f"""
73
+ <div class="impact-container">
74
+ <div class="impact-card bad-impact">
75
+ <h3 style="margin: 0 0 10px 0; color: #fca5a5;">🔥 LLM Impact (Massive Servers)</h3>
76
+ <p style="margin:5px 0; font-size:1.2em;"><strong>{impact['water']}L</strong> Water | <strong>{impact['energy']}kWh</strong> Energy</p>
77
+ <p style="margin:0; font-size:0.9em; opacity: 0.8;">Drains actual reservoirs to cool data centers.</p>
 
 
78
  </div>
79
+ <div class="impact-card good-impact">
80
+ <h3 style="margin: 0 0 10px 0; color: #86efac;">🌿 Eco-Alternative</h3>
81
+ <p style="margin:5px 0; font-size:1.2em;"><strong>~0.0L</strong> Water | <strong>~0.001kWh</strong> Energy</p>
82
+ <p style="margin:0; font-size:0.9em; opacity: 0.8;">Use local processing or targeted searches.</p>
 
83
  </div>
84
  </div>
85
  """
86
 
87
+ def format_alt_button(text):
88
+ """Adds formatting to make alternative text look like a card."""
89
+ icon = "💡"
90
+ if "Search" in text or "Google" in text: icon = "🔍"
91
+ elif "Wikipedia" in text or "encyclopedia" in text: icon = "📚"
92
+ elif "expert" in text or "friend" in text: icon = "👤"
93
+ elif "calculator" in text or "Wolfram" in text: icon = "🧮"
94
+ elif "pen" in text or "paper" in text or "notebook" in text: icon = "📝"
95
+ return f"{icon}\n{text}"
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  # --- 3. Core Game Logic ---
98
 
99
  def process_query(user_input, state):
100
  if state["game_over"] or not user_input.strip():
101
+ return [state, gr.update()] + [gr.update()]*9
102
 
 
103
  result = classifier(user_input, CATEGORIES)
104
  category = result['labels'][0]
105
  confidence = result['scores'][0]
106
 
107
  impact = calculate_impact(category, confidence, user_input)
108
 
 
109
  state["queries"] += 1
110
  state["water"] = max(0, state["water"] - impact["water"])
111
  state["energy"] += impact["energy"]
112
+
 
 
113
  show_alts = False
114
+ alt_choices = ["", "", ""]
 
115
 
116
  if category == "complex research query":
 
117
  state["points"] += 50
118
+ dialogue = "Finally, a real question! Draining my servers, but totally justified."
119
  else:
 
120
  show_alts = True
121
+ raw_alts = ALTERNATIVES.get(category, ["Google", "Wikipedia", "Book"])
122
+ state["current_best_alt"] = raw_alts[0]
123
+ random.shuffle(raw_alts)
124
+ alt_choices = [format_alt_button(a) for a in raw_alts]
125
+ state["current_shuffled_alts"] = raw_alts # Store raw text to check answers
 
126
  dialogue = get_dialogue(state["water"], category)
127
 
128
+ stats_ui = get_stats_html(state["water"], state["energy"], state["points"])
129
  drip_ui = get_drip_visuals(state["water"])
130
  impact_ui = get_impact_cards(impact, category)
131
  audio_path = generate_audio(dialogue)
132
+ feedback_text = f"### 🧠 **{category.upper()}** Detected!\n*{dialogue}*"
133
 
 
134
  if state["points"] >= 500 or state["water"] <= 0:
135
  state["game_over"] = True
136
+ return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(value="## Game Over! Check your score."), gr.update(visible=False), gr.update(), gr.update(), gr.update(), gr.update())
 
 
 
 
 
 
 
 
 
 
 
137
 
138
+ return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(value=feedback_text), gr.update(visible=show_alts),
139
+ gr.update(value=alt_choices[0]), gr.update(value=alt_choices[1]), gr.update(value=alt_choices[2]), gr.update(value=""))
140
 
141
+ def select_alternative(choice_text, state):
 
142
  if state["game_over"] or not state.get("current_best_alt"):
143
+ return state, gr.update(), gr.update(), gr.update()
144
 
145
+ # Strip the emoji from the button text to compare with the raw dictionary string
146
+ clean_choice = choice_text.split("\n")[-1]
147
+
148
+ if clean_choice == state["current_best_alt"]:
149
  state["points"] += 10
150
+ msg = " **Excellent!** +10 Points. You saved massive server resources."
 
151
  else:
152
+ msg = "⚠️ **Okay choice.** It's better than an LLM, but there was a slightly more optimal tool for this specific task."
153
 
154
+ stats_ui = get_stats_html(state["water"], state["energy"], state["points"])
 
155
 
156
+ return state, stats_ui, gr.update(value=msg), gr.update(visible=False)
 
157
 
158
+ # --- 4. Beautiful Frontend Layout ---
 
159
 
160
  custom_css = """
161
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap');
162
+
163
+ /* Main Theme */
164
+ body, .gradio-container { background-color: #0B1120 !important; font-family: 'Outfit', sans-serif !important; color: #e2e8f0 !important; }
165
+ .gradio-container { max-width: 900px !important; margin: auto; padding-top: 20px; }
166
+
167
+ /* Typography & Headers */
168
+ h1 { text-align: center; color: #38bdf8; text-shadow: 0 0 15px rgba(56, 189, 248, 0.5); font-size: 2.5em !important; margin-bottom: 5px !important;}
169
+ .subtitle { text-align: center; color: #94a3b8; margin-bottom: 30px; font-weight: 300; }
170
+
171
+ /* Dashboard Stats */
172
+ .stats-container { display: flex; gap: 15px; justify-content: space-between; margin-bottom: 20px; }
173
+ .stat-card { flex: 1; padding: 15px; border-radius: 16px; text-align: center; background: rgba(30, 41, 59, 0.6); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); }
174
+ .water-card { box-shadow: 0 4px 20px rgba(56, 189, 248, 0.15); border-top: 2px solid #38bdf8; }
175
+ .energy-card { box-shadow: 0 4px 20px rgba(250, 204, 21, 0.15); border-top: 2px solid #facc15; }
176
+ .points-card { box-shadow: 0 4px 20px rgba(52, 211, 153, 0.15); border-top: 2px solid #34d399; }
177
+ .stat-label { font-size: 0.8em; letter-spacing: 1.5px; opacity: 0.8; margin-bottom: 5px; }
178
+ .stat-value { font-size: 2em; font-weight: 700; color: white; }
179
+ .progress-bar-bg { background: #1e293b; height: 8px; border-radius: 4px; margin-top: 10px; overflow: hidden; }
180
+ .progress-bar-fill { background: linear-gradient(90deg, #0284c7, #38bdf8); height: 100%; transition: width 0.5s ease; }
181
+
182
+ /* Integrated Drip Pod (Character + Audio) */
183
+ .drip-pod { background: rgba(15, 23, 42, 0.6); border-radius: 24px; border: 1px solid rgba(56, 189, 248, 0.2); padding: 30px 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; box-shadow: inset 0 0 40px rgba(0,0,0,0.5); }
184
+ .drip-character { width: 160px; height: 160px; border-radius: 50%; background: radial-gradient(circle, rgba(56,189,248,0.15) 0%, transparent 70%); display: flex; flex-direction: column; align-items: center; justify-content: center; margin-bottom: 10px; }
185
+ @keyframes float { 0% { transform: translateY(0px); } 50% { transform: translateY(-15px); } 100% { transform: translateY(0px); } }
186
+
187
+ /* Customize the Audio Player to blend in */
188
+ .drip-audio { background: transparent !important; border: none !important; box-shadow: none !important; width: 100%; max-width: 300px; filter: invert(1) hue-rotate(180deg) brightness(1.5); /* Tricks to make default audio player match dark theme */ }
189
+
190
+ /* Input Area */
191
+ .input-row { align-items: stretch !important; margin: 20px 0; }
192
+ .custom-textbox textarea { background: rgba(30, 41, 59, 0.8) !important; border: 1px solid #38bdf8 !important; color: white !important; font-size: 1.1em !important; border-radius: 12px !important; box-shadow: 0 0 15px rgba(56, 189, 248, 0.1) !important; padding: 15px !important; }
193
+ .custom-btn { background: linear-gradient(135deg, #0284c7, #0ea5e9) !important; border: none !important; color: white !important; font-weight: bold !important; font-size: 1.1em !important; border-radius: 12px !important; transition: all 0.3s !important; }
194
+ .custom-btn:hover { box-shadow: 0 0 20px rgba(56, 189, 248, 0.6) !important; transform: scale(1.02); }
195
+
196
+ /* Impact Cards */
197
+ .impact-container { display: flex; gap: 20px; width: 100%; }
198
+ .impact-card { flex: 1; padding: 20px; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1); }
199
+ .bad-impact { background: linear-gradient(180deg, rgba(127, 29, 29, 0.3), rgba(69, 10, 10, 0.5)); border-top: 3px solid #ef4444; }
200
+ .good-impact { background: linear-gradient(180deg, rgba(20, 83, 45, 0.3), rgba(6, 78, 59, 0.5)); border-top: 3px solid #10b981; }
201
+
202
+ /* Interactive Alternative Cards */
203
+ .alt-group { padding: 20px; background: rgba(16, 185, 129, 0.05); border-radius: 16px; border: 1px dashed #10b981; margin-top: 20px;}
204
+ .alt-card { background: rgba(30, 41, 59, 0.8) !important; border: 1px solid #10b981 !important; color: #a7f3d0 !important; height: 120px !important; border-radius: 16px !important; font-size: 1.1em !important; white-space: pre-wrap !important; transition: all 0.2s ease !important; cursor: pointer; display: flex; flex-direction: column; justify-content: center; align-items: center; box-shadow: 0 4px 15px rgba(0,0,0,0.3) !important;}
205
+ .alt-card:hover { background: rgba(16, 185, 129, 0.2) !important; transform: translateY(-5px) !important; box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4) !important; color: white !important;}
206
  """
207
 
208
  with gr.Blocks(css=custom_css, title="EcoQueryQuest") as demo:
 
 
209
  game_state = gr.State({
210
  "water": 100.0, "energy": 0.0, "points": 0, "queries": 0,
211
+ "history": [], "game_over": False, "current_best_alt": None, "current_shuffled_alts": []
 
212
  })
213
 
214
+ gr.Markdown("<h1>🌍 EcoQueryQuest</h1>")
215
+ gr.Markdown("<div class='subtitle'>Type a query. Watch Drip react. Reach 500 points to win, but don't run out of water!</div>")
216
+
217
+ # TOP: Dashboard Stats
218
+ stats_html = gr.HTML(get_stats_html(100.0, 0.0, 0))
219
 
 
220
  with gr.Row():
221
+ # LEFT: Drip Character & Audio Integrated
222
+ with gr.Column(scale=1, elem_classes=["drip-pod"]):
223
  drip_html = gr.HTML(get_drip_visuals(100))
224
+ drip_audio = gr.Audio(label="Drip's Voice", autoplay=True, interactive=False, elem_classes=["drip-audio"], show_download_button=False, show_share_button=False)
 
 
 
 
 
 
 
 
 
 
 
225
 
226
+ # RIGHT: Interaction & Feedback
227
+ with gr.Column(scale=2):
228
+ with gr.Row(elem_classes=["input-row"]):
229
+ user_input = gr.Textbox(show_label=False, placeholder="Write an email to my manager asking for sick leave...", elem_classes=["custom-textbox"], scale=4)
230
+ submit_btn = gr.Button("Send Query", elem_classes=["custom-btn"], scale=1)
231
+
232
+ drip_feedback = gr.Markdown("### Waiting for your first query...")
233
+ impact_display = gr.HTML()
234
 
235
+ # BOTTOM: Interactive Alternatives Section
236
+ with gr.Group(visible=False, elem_classes=["alt-group"]) as alternatives_group:
237
+ gr.Markdown("<h3 style='text-align:center; color:#34d399; margin-bottom: 15px;'>🌱 Drip says: 'Quick! Pick a greener tool to earn points!'</h3>")
238
  with gr.Row():
239
+ alt_btn_1 = gr.Button("", elem_classes=["alt-card"])
240
+ alt_btn_2 = gr.Button("", elem_classes=["alt-card"])
241
+ alt_btn_3 = gr.Button("", elem_classes=["alt-card"])
242
+ alt_feedback = gr.Markdown(elem_classes=["glow-text"])
243
 
244
  # EVENT WIRING
 
 
245
  submit_btn.click(
246
  fn=process_query,
247
  inputs=[user_input, game_state],
248
+ outputs=[game_state, stats_html, drip_html, drip_audio, impact_display, drip_feedback, alternatives_group, alt_btn_1, alt_btn_2, alt_btn_3, alt_feedback]
 
 
 
 
249
  )
250
 
 
251
  for btn in [alt_btn_1, alt_btn_2, alt_btn_3]:
252
  btn.click(
253
  fn=select_alternative,
254
  inputs=[btn, game_state],
255
+ outputs=[game_state, stats_html, alt_feedback, alternatives_group]
 
 
 
 
256
  )
257
 
 
258
  if __name__ == "__main__":
259
  demo.launch()