vighnesh-shetty-vs commited on
Commit
ad0eb53
·
1 Parent(s): 8caf4f6

Add updated files

Browse files
Files changed (2) hide show
  1. app.py +37 -59
  2. game_data.py +15 -18
app.py CHANGED
@@ -24,7 +24,7 @@ def generate_audio(text):
24
  tts.save(filepath)
25
  return filepath
26
 
27
- def get_stats_html(water, energy, points):
28
  return f"""
29
  <div class="stats-container">
30
  <div class="stat-card water-card">
@@ -38,6 +38,10 @@ def get_stats_html(water, energy, points):
38
  <div class="stat-label">ENERGY WASTED</div>
39
  <div class="stat-value">{energy:.4f} <span style="font-size: 0.5em;">kWh</span></div>
40
  </div>
 
 
 
 
41
  <div class="stat-card points-card">
42
  <div class="stat-label">SCORE</div>
43
  <div class="stat-value">{points}</div>
@@ -46,33 +50,27 @@ def get_stats_html(water, energy, points):
46
  """
47
 
48
  def get_drip_visuals(water_level):
49
- """Generates the advanced SVG Drip character with dynamic expressions."""
50
-
51
  if water_level > 70:
52
- # Happy, Plump
53
  g_start, g_end = "#7dd3fc", "#0369a1"
54
- mouth_d = "M43 112 Q60 122 77 112" # Smile
55
  cracks_op, sweat_op, tear_op = "0", "0", "0"
56
  mood, mood_color = "Hydrated and (barely) coping", "rgb(34, 211, 238)"
57
  anim = "bounce 2s infinite ease-in-out"
58
  elif 30 <= water_level <= 70:
59
- # Tired, Sweaty
60
  g_start, g_end = "#38bdf8", "#0284c7"
61
- mouth_d = "M43 112 Q60 110 77 112" # Flat / Unamused
62
- cracks_op, sweat_op, tear_op = "0", "1", "0" # Show sweat
63
  mood, mood_color = "Shrinking & Sweaty", "#0ea5e9"
64
  anim = "float 4s infinite ease-in-out"
65
  elif water_level > 0:
66
- # Overheating, Red, Cracked, Crying
67
  g_start, g_end = "#fca5a5", "#b91c1c"
68
- mouth_d = "M43 116 Q60 102 77 116" # Frown
69
- cracks_op, sweat_op, tear_op = "1", "0", "1" # Show cracks and tear
70
  mood, mood_color = "Overheating & Raspy", "#ef4444"
71
  anim = "shake 0.5s infinite"
72
  else:
73
- # Evaporated
74
- g_start, g_end = "#94a3b8", "#334155" # Gray out
75
- mouth_d = "M40 112 Q45 105 50 112 T60 112 T70 112 T80 112" # Squiggly mouth
76
  cracks_op, sweat_op, tear_op = "0", "0", "0"
77
  mood, mood_color = "Evaporated!", "#94a3b8"
78
  anim = "floatUp 3s forwards"
@@ -80,7 +78,6 @@ def get_drip_visuals(water_level):
80
  return f"""
81
  <div class="drip-panel" style="animation: {anim}; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%;">
82
  <div class="drip-lbl" style="font-weight: 800; color: #94a3b8; letter-spacing: 3px; font-size: 14px; margin-bottom: 5px;">DRIP</div>
83
-
84
  <svg id="drip-svg" viewBox="0 0 120 148" style="width: 140px; height: 160px; overflow: visible;" xmlns="http://www.w3.org/2000/svg">
85
  <defs>
86
  <radialGradient id="dg" cx="38%" cy="32%">
@@ -88,34 +85,23 @@ def get_drip_visuals(water_level):
88
  <stop id="dg2" offset="100%" stop-color="{g_end}"></stop>
89
  </radialGradient>
90
  </defs>
91
-
92
  <path id="dbody" d="M60 8 C60 8,102 66,102 90 C102 118,83 137,60 137 C37 137,18 118,18 90 C18 66,60 8,60 8Z" fill="url(#dg)" stroke="rgba(34,211,238,0.35)" stroke-width="1.5" opacity="1"></path>
93
-
94
  <ellipse cx="42" cy="70" rx="9" ry="13" fill="rgba(255,255,255,0.22)" transform="rotate(-18,42,70)"></ellipse>
95
-
96
  <circle cx="44" cy="90" r="10" fill="white"></circle>
97
  <circle cx="76" cy="90" r="10" fill="white"></circle>
98
-
99
  <circle id="pl" cx="45" cy="91" r="6" fill="#0f172a"></circle>
100
  <circle id="pr" cx="77" cy="91" r="6" fill="#0f172a"></circle>
101
-
102
  <circle cx="47" cy="88" r="2.5" fill="white"></circle>
103
  <circle cx="79" cy="88" r="2.5" fill="white"></circle>
104
-
105
  <path id="dm" d="{mouth_d}" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round" fill="none"></path>
106
-
107
  <g id="cracks" opacity="{cracks_op}" style="transition: opacity 0.5s;">
108
  <path d="M80 46 L86 58 L79 65 L85 77" stroke="#bae6fd" stroke-width="1.3" stroke-linecap="round" fill="none" opacity=".7"></path>
109
  <path d="M37 82 L31 93 L39 99" stroke="#bae6fd" stroke-width="1.3" stroke-linecap="round" fill="none" opacity=".7"></path>
110
  </g>
111
-
112
  <path id="sweat" d="M108 76 C110 70,116 70,116 78 C116 84,110 88,108 82Z" fill="#7dd3fc" opacity="{sweat_op}" style="transition: opacity 0.5s;"></path>
113
-
114
  <path id="tear" d="M38 104 C37 108,33 108,33 112 C33 115,36 117,38 114 C40 117,43 115,43 112 C43 108,39 108,38 104Z" fill="#93c5fd" opacity="{tear_op}" style="transition: opacity 0.5s;"></path>
115
  </svg>
116
-
117
  <div class="drip-mood" id="drip-mood" style="color: {mood_color}; font-weight: 500; font-size: 1.1rem; margin-top: 15px;">{mood}</div>
118
-
119
  <div style="text-align:center; margin-top:10px;">
120
  <div style="font-size:10px; color:#94a3b8; text-transform:uppercase; letter-spacing:1px;">Water Left</div>
121
  <div style="font-size:26px; font-weight:700; color:{mood_color};" id="d-water">{water_level:.2f}L</div>
@@ -128,15 +114,17 @@ def get_impact_cards(impact, category):
128
  <div class="impact-container">
129
  <div class="impact-card bad-impact">
130
  <h3 style="margin: 0 0 10px 0; color: #fca5a5;">🔥 LLM Impact</h3>
131
- <p style="margin:5px 0; font-size:1.2em;"><strong>{impact['water_ml']} mL</strong> Water</p>
132
- <p style="margin:5px 0; font-size:1.2em;"><strong>{impact['energy_wh']} Wh</strong> Energy</p>
133
- <p style="margin:0; font-size:0.9em; opacity: 0.8;">Deducted from global reserves.</p>
 
134
  </div>
135
  <div class="impact-card good-impact">
136
  <h3 style="margin: 0 0 10px 0; color: #86efac;">🌿 Eco-Alternative</h3>
137
- <p style="margin:5px 0; font-size:1.2em;"><strong>~0.0 mL</strong> Water</p>
138
- <p style="margin:5px 0; font-size:1.2em;"><strong>~0.001 Wh</strong> Energy</p>
139
- <p style="margin:0; font-size:0.9em; opacity: 0.8;">Near-zero footprint.</p>
 
140
  </div>
141
  </div>
142
  """
@@ -152,7 +140,8 @@ def format_alt_button(text):
152
 
153
  def process_query(user_input, selected_model, state):
154
  if state["game_over"] or not user_input.strip():
155
- return [state, gr.update()] + [gr.update()]*9
 
156
 
157
  result = classifier(user_input, CATEGORIES)
158
  category = result['labels'][0]
@@ -163,6 +152,7 @@ def process_query(user_input, selected_model, state):
163
  state["queries"] += 1
164
  state["water"] = max(0, state["water"] - impact["water_l"])
165
  state["energy"] += impact["energy_kwh"]
 
166
 
167
  show_alts = False
168
  alt_choices = ["", "", ""]
@@ -179,7 +169,7 @@ def process_query(user_input, selected_model, state):
179
  alt_choices = [format_alt_button(a["text"]) for a in raw_alts]
180
  dialogue = get_dialogue(state["water"], category)
181
 
182
- stats_ui = get_stats_html(state["water"], state["energy"], state["points"])
183
  drip_ui = get_drip_visuals(state["water"])
184
  impact_ui = get_impact_cards(impact, category)
185
  audio_path = generate_audio(dialogue)
@@ -187,10 +177,11 @@ def process_query(user_input, selected_model, state):
187
 
188
  if state["points"] >= 500 or state["water"] <= 0:
189
  state["game_over"] = True
190
- return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(value="<div class='feedback-box'>Game Over! Check your score.</div>"), gr.update(visible=False), gr.update(), gr.update(), gr.update(), gr.update())
191
 
 
192
  return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(value=feedback_text), gr.update(visible=show_alts),
193
- gr.update(value=alt_choices[0]), gr.update(value=alt_choices[1]), gr.update(value=alt_choices[2]), gr.update(value=""))
194
 
195
  def select_alternative(choice_text, state):
196
  if state["game_over"] or not state.get("current_best_alt"):
@@ -205,12 +196,10 @@ def select_alternative(choice_text, state):
205
  else:
206
  msg = f"⚠️ **Okay choice.** Better than an LLM, but there was a slightly more optimal tool.\n\n<a href='{selected_dict['url']}' target='_blank' style='display:inline-block; margin-top:10px; padding: 10px 20px; background:#38bdf8; color:white; text-decoration:none; border-radius:8px; font-weight:bold;'>👉 Click here to use {clean_choice}</a>"
207
 
208
- stats_ui = get_stats_html(state["water"], state["energy"], state["points"])
209
 
210
  return state, stats_ui, gr.update(value=msg), gr.update(visible=False)
211
 
212
-
213
- # --- UI Styling ---
214
  custom_css = """
215
  @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap');
216
  body, .gradio-container { background-color: #0B1120 !important; font-family: 'Outfit', sans-serif !important; color: #e2e8f0 !important; }
@@ -218,44 +207,36 @@ body, .gradio-container { background-color: #0B1120 !important; font-family: 'Ou
218
  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;}
219
  .subtitle { text-align: center; color: #94a3b8; margin-bottom: 30px; font-weight: 300; }
220
 
221
- /* Dashboard */
222
- .stats-container { display: flex; gap: 15px; justify-content: space-between; margin-bottom: 20px; }
223
- .stat-card { flex: 1; padding: 15px; border-radius: 16px; text-align: center; background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(255,255,255,0.1); }
224
  .water-card { box-shadow: 0 4px 20px rgba(56, 189, 248, 0.15); border-top: 2px solid #38bdf8; }
225
  .energy-card { box-shadow: 0 4px 20px rgba(250, 204, 21, 0.15); border-top: 2px solid #facc15; }
 
226
  .points-card { box-shadow: 0 4px 20px rgba(52, 211, 153, 0.15); border-top: 2px solid #34d399; }
227
- .stat-label { font-size: 0.8em; letter-spacing: 1.5px; opacity: 0.8; margin-bottom: 5px; }
228
- .stat-value { font-size: 2em; font-weight: 700; color: white; }
229
  .progress-bar-bg { background: #1e293b; height: 8px; border-radius: 4px; margin-top: 10px; overflow: hidden; }
230
  .progress-bar-fill { background: linear-gradient(90deg, #0284c7, #38bdf8); height: 100%; transition: width 0.5s ease; }
231
 
232
- /* SVG Animations */
233
  @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-15px); } }
234
  @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
235
  @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
236
  @keyframes floatUp { to { transform: translateY(-30px); opacity: 0; } }
237
 
238
- /* Drip Pod Column */
239
  .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; box-shadow: inset 0 0 40px rgba(0,0,0,0.5); }
240
- .drip-character-container { display: flex; flex-direction: column; align-items: center; justify-content: center; margin-bottom: 20px; min-height: 180px; }
241
  .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); margin-bottom: 15px;}
242
-
243
- /* Feedback Text Moved to Left Column */
244
  .feedback-box { background: rgba(56, 189, 248, 0.1); border-left: 4px solid #38bdf8; padding: 15px; border-radius: 8px; font-size: 1.05em; color: #e2e8f0; width: 100%; text-align: left; line-height: 1.4; box-sizing: border-box;}
245
 
246
- /* Input Area */
247
  .input-row { align-items: stretch !important; margin: 10px 0; }
248
  .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: inset 0 2px 10px rgba(0,0,0,0.3) !important; padding: 15px !important; resize: none !important; }
249
  .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; height: 100%; min-height: 85px; }
250
  .custom-btn:hover { box-shadow: 0 0 20px rgba(56, 189, 248, 0.6) !important; transform: scale(1.02); }
251
 
252
- /* Impact Cards */
253
  .impact-container { display: flex; gap: 20px; width: 100%; }
254
  .impact-card { flex: 1; padding: 20px; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1); }
255
  .bad-impact { background: linear-gradient(180deg, rgba(127, 29, 29, 0.3), rgba(69, 10, 10, 0.5)); border-top: 3px solid #ef4444; }
256
  .good-impact { background: linear-gradient(180deg, rgba(20, 83, 45, 0.3), rgba(6, 78, 59, 0.5)); border-top: 3px solid #10b981; }
257
 
258
- /* Interactive Alternatives Group */
259
  .alt-group { padding: 25px; background: linear-gradient(145deg, rgba(16, 185, 129, 0.05), rgba(6, 95, 70, 0.1)); border-radius: 20px; border: 1px solid rgba(16, 185, 129, 0.3); margin-top: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.2);}
260
  .alt-card { background: linear-gradient(145deg, #1e293b, #0f172a) !important; border: 1px solid #10b981 !important; color: #a7f3d0 !important; height: 130px !important; border-radius: 16px !important; font-size: 1.15em !important; font-weight: 500 !important; white-space: pre-wrap !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.4) !important;}
261
  .alt-card:hover { background: linear-gradient(145deg, #064e3b, #065f46) !important; transform: translateY(-8px) !important; box-shadow: 0 12px 30px rgba(16, 185, 129, 0.3) !important; color: white !important; border-color: #34d399 !important;}
@@ -264,34 +245,30 @@ h1 { text-align: center; color: #38bdf8; text-shadow: 0 0 15px rgba(56, 189, 248
264
 
265
  with gr.Blocks(title="EcoQueryQuest") as demo:
266
  game_state = gr.State({
267
- "water": 100.0, "energy": 0.0, "points": 0, "queries": 0,
268
  "history": [], "game_over": False, "current_best_alt": None, "current_shuffled_alts": []
269
  })
270
 
271
  gr.Markdown("<h1>🌍 EcoQueryQuest</h1>")
272
  gr.Markdown("<div class='subtitle'>Select a model, type a query, and watch Drip react. Reach 500 points to win!</div>")
273
 
274
- stats_html = gr.HTML(get_stats_html(100.0, 0.0, 0))
275
 
276
  with gr.Row():
277
- # LEFT COLUMN: Drip character, Audio, and Feedback Text
278
  with gr.Column(scale=1, elem_classes=["drip-pod"]):
279
  drip_html = gr.HTML(get_drip_visuals(100))
280
  drip_audio = gr.Audio(label="Drip's Voice", autoplay=True, interactive=False, elem_classes=["drip-audio"])
281
  drip_feedback = gr.HTML("<div class='feedback-box'>Waiting for your first query...</div>")
282
 
283
- # RIGHT COLUMN: Interactions and Impact Data
284
  with gr.Column(scale=2):
285
  model_selector = gr.Dropdown(choices=list(MODELS.keys()), value="GPT-5.4", label="Select Target LLM Backend", elem_classes=["custom-dropdown"])
286
 
287
  with gr.Row(elem_classes=["input-row"]):
288
- # Changed lines=3 so the query displays nicely without being cut off
289
  user_input = gr.Textbox(show_label=False, placeholder="Type your query here...\n(Press Shift+Enter for new line)", elem_classes=["custom-textbox"], scale=4, lines=3)
290
  submit_btn = gr.Button("Send Query", elem_classes=["custom-btn"], scale=1)
291
 
292
  impact_display = gr.HTML()
293
 
294
- # BOTTOM: Beautiful Interactive Alternatives
295
  with gr.Group(visible=False, elem_classes=["alt-group"]) as alternatives_group:
296
  gr.Markdown("<h3 style='text-align:center; color:#34d399; margin-bottom: 20px;'>🌱 Drip says: 'Quick! Pick a greener tool to earn points!'</h3>")
297
  with gr.Row():
@@ -300,10 +277,11 @@ with gr.Blocks(title="EcoQueryQuest") as demo:
300
  alt_btn_3 = gr.Button("", elem_classes=["alt-card"])
301
  alt_feedback = gr.Markdown()
302
 
 
303
  submit_btn.click(
304
  fn=process_query,
305
  inputs=[user_input, model_selector, game_state],
306
- 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]
307
  )
308
 
309
  for btn in [alt_btn_1, alt_btn_2, alt_btn_3]:
 
24
  tts.save(filepath)
25
  return filepath
26
 
27
+ def get_stats_html(water, energy, co2, points):
28
  return f"""
29
  <div class="stats-container">
30
  <div class="stat-card water-card">
 
38
  <div class="stat-label">ENERGY WASTED</div>
39
  <div class="stat-value">{energy:.4f} <span style="font-size: 0.5em;">kWh</span></div>
40
  </div>
41
+ <div class="stat-card co2-card">
42
+ <div class="stat-label">CARBON (CO₂e)</div>
43
+ <div class="stat-value">{co2:.2f}g</div>
44
+ </div>
45
  <div class="stat-card points-card">
46
  <div class="stat-label">SCORE</div>
47
  <div class="stat-value">{points}</div>
 
50
  """
51
 
52
  def get_drip_visuals(water_level):
 
 
53
  if water_level > 70:
 
54
  g_start, g_end = "#7dd3fc", "#0369a1"
55
+ mouth_d = "M43 112 Q60 122 77 112"
56
  cracks_op, sweat_op, tear_op = "0", "0", "0"
57
  mood, mood_color = "Hydrated and (barely) coping", "rgb(34, 211, 238)"
58
  anim = "bounce 2s infinite ease-in-out"
59
  elif 30 <= water_level <= 70:
 
60
  g_start, g_end = "#38bdf8", "#0284c7"
61
+ mouth_d = "M43 112 Q60 110 77 112"
62
+ cracks_op, sweat_op, tear_op = "0", "1", "0"
63
  mood, mood_color = "Shrinking & Sweaty", "#0ea5e9"
64
  anim = "float 4s infinite ease-in-out"
65
  elif water_level > 0:
 
66
  g_start, g_end = "#fca5a5", "#b91c1c"
67
+ mouth_d = "M43 116 Q60 102 77 116"
68
+ cracks_op, sweat_op, tear_op = "1", "0", "1"
69
  mood, mood_color = "Overheating & Raspy", "#ef4444"
70
  anim = "shake 0.5s infinite"
71
  else:
72
+ g_start, g_end = "#94a3b8", "#334155"
73
+ mouth_d = "M40 112 Q45 105 50 112 T60 112 T70 112 T80 112"
 
74
  cracks_op, sweat_op, tear_op = "0", "0", "0"
75
  mood, mood_color = "Evaporated!", "#94a3b8"
76
  anim = "floatUp 3s forwards"
 
78
  return f"""
79
  <div class="drip-panel" style="animation: {anim}; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%;">
80
  <div class="drip-lbl" style="font-weight: 800; color: #94a3b8; letter-spacing: 3px; font-size: 14px; margin-bottom: 5px;">DRIP</div>
 
81
  <svg id="drip-svg" viewBox="0 0 120 148" style="width: 140px; height: 160px; overflow: visible;" xmlns="http://www.w3.org/2000/svg">
82
  <defs>
83
  <radialGradient id="dg" cx="38%" cy="32%">
 
85
  <stop id="dg2" offset="100%" stop-color="{g_end}"></stop>
86
  </radialGradient>
87
  </defs>
 
88
  <path id="dbody" d="M60 8 C60 8,102 66,102 90 C102 118,83 137,60 137 C37 137,18 118,18 90 C18 66,60 8,60 8Z" fill="url(#dg)" stroke="rgba(34,211,238,0.35)" stroke-width="1.5" opacity="1"></path>
 
89
  <ellipse cx="42" cy="70" rx="9" ry="13" fill="rgba(255,255,255,0.22)" transform="rotate(-18,42,70)"></ellipse>
 
90
  <circle cx="44" cy="90" r="10" fill="white"></circle>
91
  <circle cx="76" cy="90" r="10" fill="white"></circle>
 
92
  <circle id="pl" cx="45" cy="91" r="6" fill="#0f172a"></circle>
93
  <circle id="pr" cx="77" cy="91" r="6" fill="#0f172a"></circle>
 
94
  <circle cx="47" cy="88" r="2.5" fill="white"></circle>
95
  <circle cx="79" cy="88" r="2.5" fill="white"></circle>
 
96
  <path id="dm" d="{mouth_d}" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round" fill="none"></path>
 
97
  <g id="cracks" opacity="{cracks_op}" style="transition: opacity 0.5s;">
98
  <path d="M80 46 L86 58 L79 65 L85 77" stroke="#bae6fd" stroke-width="1.3" stroke-linecap="round" fill="none" opacity=".7"></path>
99
  <path d="M37 82 L31 93 L39 99" stroke="#bae6fd" stroke-width="1.3" stroke-linecap="round" fill="none" opacity=".7"></path>
100
  </g>
 
101
  <path id="sweat" d="M108 76 C110 70,116 70,116 78 C116 84,110 88,108 82Z" fill="#7dd3fc" opacity="{sweat_op}" style="transition: opacity 0.5s;"></path>
 
102
  <path id="tear" d="M38 104 C37 108,33 108,33 112 C33 115,36 117,38 114 C40 117,43 115,43 112 C43 108,39 108,38 104Z" fill="#93c5fd" opacity="{tear_op}" style="transition: opacity 0.5s;"></path>
103
  </svg>
 
104
  <div class="drip-mood" id="drip-mood" style="color: {mood_color}; font-weight: 500; font-size: 1.1rem; margin-top: 15px;">{mood}</div>
 
105
  <div style="text-align:center; margin-top:10px;">
106
  <div style="font-size:10px; color:#94a3b8; text-transform:uppercase; letter-spacing:1px;">Water Left</div>
107
  <div style="font-size:26px; font-weight:700; color:{mood_color};" id="d-water">{water_level:.2f}L</div>
 
114
  <div class="impact-container">
115
  <div class="impact-card bad-impact">
116
  <h3 style="margin: 0 0 10px 0; color: #fca5a5;">🔥 LLM Impact</h3>
117
+ <p style="margin:4px 0; font-size:1.1em;"><strong>{impact['water_ml']} mL</strong> Water</p>
118
+ <p style="margin:4px 0; font-size:1.1em;"><strong>{impact['energy_wh']} Wh</strong> Energy</p>
119
+ <p style="margin:4px 0; font-size:1.1em;"><strong>{impact['co2_g']} g</strong> CO₂e</p>
120
+ <p style="margin:0; font-size:0.85em; opacity: 0.8;">Deducted from global reserves.</p>
121
  </div>
122
  <div class="impact-card good-impact">
123
  <h3 style="margin: 0 0 10px 0; color: #86efac;">🌿 Eco-Alternative</h3>
124
+ <p style="margin:4px 0; font-size:1.1em;"><strong>~0.0 mL</strong> Water</p>
125
+ <p style="margin:4px 0; font-size:1.1em;"><strong>~0.001 Wh</strong> Energy</p>
126
+ <p style="margin:4px 0; font-size:1.1em;"><strong>~0.01 g</strong> CO₂e</p>
127
+ <p style="margin:0; font-size:0.85em; opacity: 0.8;">Near-zero footprint.</p>
128
  </div>
129
  </div>
130
  """
 
140
 
141
  def process_query(user_input, selected_model, state):
142
  if state["game_over"] or not user_input.strip():
143
+ # Notice we are returning an extra update at the end for the user_input box
144
+ return [state, gr.update()] + [gr.update()]*9 + [gr.update()]
145
 
146
  result = classifier(user_input, CATEGORIES)
147
  category = result['labels'][0]
 
152
  state["queries"] += 1
153
  state["water"] = max(0, state["water"] - impact["water_l"])
154
  state["energy"] += impact["energy_kwh"]
155
+ state["co2"] += impact["co2_g"]
156
 
157
  show_alts = False
158
  alt_choices = ["", "", ""]
 
169
  alt_choices = [format_alt_button(a["text"]) for a in raw_alts]
170
  dialogue = get_dialogue(state["water"], category)
171
 
172
+ stats_ui = get_stats_html(state["water"], state["energy"], state["co2"], state["points"])
173
  drip_ui = get_drip_visuals(state["water"])
174
  impact_ui = get_impact_cards(impact, category)
175
  audio_path = generate_audio(dialogue)
 
177
 
178
  if state["points"] >= 500 or state["water"] <= 0:
179
  state["game_over"] = True
180
+ return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(value="<div class='feedback-box'>Game Over! Check your score.</div>"), gr.update(visible=False), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(value=""))
181
 
182
+ # The 12th return item is the empty string to clear the user_input
183
  return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(value=feedback_text), gr.update(visible=show_alts),
184
+ gr.update(value=alt_choices[0]), gr.update(value=alt_choices[1]), gr.update(value=alt_choices[2]), gr.update(value=""), gr.update(value=""))
185
 
186
  def select_alternative(choice_text, state):
187
  if state["game_over"] or not state.get("current_best_alt"):
 
196
  else:
197
  msg = f"⚠️ **Okay choice.** Better than an LLM, but there was a slightly more optimal tool.\n\n<a href='{selected_dict['url']}' target='_blank' style='display:inline-block; margin-top:10px; padding: 10px 20px; background:#38bdf8; color:white; text-decoration:none; border-radius:8px; font-weight:bold;'>👉 Click here to use {clean_choice}</a>"
198
 
199
+ stats_ui = get_stats_html(state["water"], state["energy"], state["co2"], state["points"])
200
 
201
  return state, stats_ui, gr.update(value=msg), gr.update(visible=False)
202
 
 
 
203
  custom_css = """
204
  @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap');
205
  body, .gradio-container { background-color: #0B1120 !important; font-family: 'Outfit', sans-serif !important; color: #e2e8f0 !important; }
 
207
  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;}
208
  .subtitle { text-align: center; color: #94a3b8; margin-bottom: 30px; font-weight: 300; }
209
 
210
+ .stats-container { display: flex; gap: 10px; justify-content: space-between; margin-bottom: 20px; }
211
+ .stat-card { flex: 1; padding: 15px 10px; border-radius: 12px; text-align: center; background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(255,255,255,0.1); }
 
212
  .water-card { box-shadow: 0 4px 20px rgba(56, 189, 248, 0.15); border-top: 2px solid #38bdf8; }
213
  .energy-card { box-shadow: 0 4px 20px rgba(250, 204, 21, 0.15); border-top: 2px solid #facc15; }
214
+ .co2-card { box-shadow: 0 4px 20px rgba(168, 162, 158, 0.15); border-top: 2px solid #a8a29e; }
215
  .points-card { box-shadow: 0 4px 20px rgba(52, 211, 153, 0.15); border-top: 2px solid #34d399; }
216
+ .stat-label { font-size: 0.75em; letter-spacing: 1px; opacity: 0.8; margin-bottom: 5px; }
217
+ .stat-value { font-size: 1.8em; font-weight: 700; color: white; }
218
  .progress-bar-bg { background: #1e293b; height: 8px; border-radius: 4px; margin-top: 10px; overflow: hidden; }
219
  .progress-bar-fill { background: linear-gradient(90deg, #0284c7, #38bdf8); height: 100%; transition: width 0.5s ease; }
220
 
 
221
  @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-15px); } }
222
  @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
223
  @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
224
  @keyframes floatUp { to { transform: translateY(-30px); opacity: 0; } }
225
 
 
226
  .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; box-shadow: inset 0 0 40px rgba(0,0,0,0.5); }
 
227
  .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); margin-bottom: 15px;}
 
 
228
  .feedback-box { background: rgba(56, 189, 248, 0.1); border-left: 4px solid #38bdf8; padding: 15px; border-radius: 8px; font-size: 1.05em; color: #e2e8f0; width: 100%; text-align: left; line-height: 1.4; box-sizing: border-box;}
229
 
 
230
  .input-row { align-items: stretch !important; margin: 10px 0; }
231
  .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: inset 0 2px 10px rgba(0,0,0,0.3) !important; padding: 15px !important; resize: none !important; }
232
  .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; height: 100%; min-height: 85px; }
233
  .custom-btn:hover { box-shadow: 0 0 20px rgba(56, 189, 248, 0.6) !important; transform: scale(1.02); }
234
 
 
235
  .impact-container { display: flex; gap: 20px; width: 100%; }
236
  .impact-card { flex: 1; padding: 20px; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1); }
237
  .bad-impact { background: linear-gradient(180deg, rgba(127, 29, 29, 0.3), rgba(69, 10, 10, 0.5)); border-top: 3px solid #ef4444; }
238
  .good-impact { background: linear-gradient(180deg, rgba(20, 83, 45, 0.3), rgba(6, 78, 59, 0.5)); border-top: 3px solid #10b981; }
239
 
 
240
  .alt-group { padding: 25px; background: linear-gradient(145deg, rgba(16, 185, 129, 0.05), rgba(6, 95, 70, 0.1)); border-radius: 20px; border: 1px solid rgba(16, 185, 129, 0.3); margin-top: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.2);}
241
  .alt-card { background: linear-gradient(145deg, #1e293b, #0f172a) !important; border: 1px solid #10b981 !important; color: #a7f3d0 !important; height: 130px !important; border-radius: 16px !important; font-size: 1.15em !important; font-weight: 500 !important; white-space: pre-wrap !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.4) !important;}
242
  .alt-card:hover { background: linear-gradient(145deg, #064e3b, #065f46) !important; transform: translateY(-8px) !important; box-shadow: 0 12px 30px rgba(16, 185, 129, 0.3) !important; color: white !important; border-color: #34d399 !important;}
 
245
 
246
  with gr.Blocks(title="EcoQueryQuest") as demo:
247
  game_state = gr.State({
248
+ "water": 100.0, "energy": 0.0, "co2": 0.0, "points": 0, "queries": 0,
249
  "history": [], "game_over": False, "current_best_alt": None, "current_shuffled_alts": []
250
  })
251
 
252
  gr.Markdown("<h1>🌍 EcoQueryQuest</h1>")
253
  gr.Markdown("<div class='subtitle'>Select a model, type a query, and watch Drip react. Reach 500 points to win!</div>")
254
 
255
+ stats_html = gr.HTML(get_stats_html(100.0, 0.0, 0.0, 0))
256
 
257
  with gr.Row():
 
258
  with gr.Column(scale=1, elem_classes=["drip-pod"]):
259
  drip_html = gr.HTML(get_drip_visuals(100))
260
  drip_audio = gr.Audio(label="Drip's Voice", autoplay=True, interactive=False, elem_classes=["drip-audio"])
261
  drip_feedback = gr.HTML("<div class='feedback-box'>Waiting for your first query...</div>")
262
 
 
263
  with gr.Column(scale=2):
264
  model_selector = gr.Dropdown(choices=list(MODELS.keys()), value="GPT-5.4", label="Select Target LLM Backend", elem_classes=["custom-dropdown"])
265
 
266
  with gr.Row(elem_classes=["input-row"]):
 
267
  user_input = gr.Textbox(show_label=False, placeholder="Type your query here...\n(Press Shift+Enter for new line)", elem_classes=["custom-textbox"], scale=4, lines=3)
268
  submit_btn = gr.Button("Send Query", elem_classes=["custom-btn"], scale=1)
269
 
270
  impact_display = gr.HTML()
271
 
 
272
  with gr.Group(visible=False, elem_classes=["alt-group"]) as alternatives_group:
273
  gr.Markdown("<h3 style='text-align:center; color:#34d399; margin-bottom: 20px;'>🌱 Drip says: 'Quick! Pick a greener tool to earn points!'</h3>")
274
  with gr.Row():
 
277
  alt_btn_3 = gr.Button("", elem_classes=["alt-card"])
278
  alt_feedback = gr.Markdown()
279
 
280
+ # NOTE: Added user_input to the outputs list so we can update/clear it
281
  submit_btn.click(
282
  fn=process_query,
283
  inputs=[user_input, model_selector, game_state],
284
+ 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, user_input]
285
  )
286
 
287
  for btn in [alt_btn_1, alt_btn_2, alt_btn_3]:
game_data.py CHANGED
@@ -1,29 +1,27 @@
1
  # Model Baselines (per 1000 tokens)
2
  MODELS = {
3
- "Mistral Large 3": {"energy_wh": 1.98, "water_ml": 3.56},
4
- "Mistral Medium 3": {"energy_wh": 6.76, "water_ml": 12.17},
5
- "Claude Sonnet 4.6": {"energy_wh": 19.32, "water_ml": 34.78},
6
- "Gemini 3 Pro": {"energy_wh": 72.45, "water_ml": 130.41},
7
- "Gemini 3.1 Pro": {"energy_wh": 77.28, "water_ml": 139.10},
8
- "Claude Opus 4.6": {"energy_wh": 96.60, "water_ml": 173.88},
9
- "GPT-5.2": {"energy_wh": 144.90, "water_ml": 260.82},
10
- "GPT-5.4": {"energy_wh": 169.05, "water_ml": 304.29}
11
  }
12
 
13
  # Category Token Multipliers
14
  CATEGORY_MULTIPLIERS = {
15
- "simple factual question": 0.05, # 50 tokens
16
- "mathematical calculation": 0.10, # 100 tokens
17
- "creative content generation": 0.80, # 800 tokens
18
- "complex research query": 1.50 # 1500 tokens
19
  }
20
 
21
  def calculate_impact(category, confidence_score, query_text, model_name):
22
- # Get base costs
23
  model_cost = MODELS.get(model_name, MODELS["Mistral Large 3"])
24
  cat_mult = CATEGORY_MULTIPLIERS.get(category, 0.05)
25
 
26
- # 1. Confidence Multiplier
27
  if confidence_score > 0.8:
28
  conf_mult = 1.0
29
  elif 0.5 <= confidence_score <= 0.8:
@@ -31,7 +29,6 @@ def calculate_impact(category, confidence_score, query_text, model_name):
31
  else:
32
  conf_mult = 1.5
33
 
34
- # 2. Length Factor
35
  word_count = len(query_text.split())
36
  if word_count < 20:
37
  len_factor = 1.0
@@ -40,11 +37,10 @@ def calculate_impact(category, confidence_score, query_text, model_name):
40
  else:
41
  len_factor = 1.5
42
 
43
- # 3. Final Calculation Formula
44
  water_ml = model_cost["water_ml"] * cat_mult * conf_mult * len_factor
45
  energy_wh = model_cost["energy_wh"] * cat_mult * conf_mult * len_factor
 
46
 
47
- # Convert for main tank UI (Liters and kWh)
48
  water_l = water_ml / 1000.0
49
  energy_kwh = energy_wh / 1000.0
50
 
@@ -52,5 +48,6 @@ def calculate_impact(category, confidence_score, query_text, model_name):
52
  "water_l": round(water_l, 4),
53
  "energy_kwh": round(energy_kwh, 5),
54
  "water_ml": round(water_ml, 1),
55
- "energy_wh": round(energy_wh, 2)
 
56
  }
 
1
  # Model Baselines (per 1000 tokens)
2
  MODELS = {
3
+ "Mistral Large 3": {"energy_wh": 1.98, "water_ml": 3.56, "co2_g": 0.95},
4
+ "Mistral Medium 3": {"energy_wh": 6.76, "water_ml": 12.17, "co2_g": 3.24},
5
+ "Claude Sonnet 4.6": {"energy_wh": 19.32, "water_ml": 34.78, "co2_g": 9.27},
6
+ "Gemini 3 Pro": {"energy_wh": 72.45, "water_ml": 130.41, "co2_g": 34.78},
7
+ "Gemini 3.1 Pro": {"energy_wh": 77.28, "water_ml": 139.10, "co2_g": 37.09},
8
+ "Claude Opus 4.6": {"energy_wh": 96.60, "water_ml": 173.88, "co2_g": 46.37},
9
+ "GPT-5.2": {"energy_wh": 144.90, "water_ml": 260.82, "co2_g": 69.55},
10
+ "GPT-5.4": {"energy_wh": 169.05, "water_ml": 304.29, "co2_g": 81.14}
11
  }
12
 
13
  # Category Token Multipliers
14
  CATEGORY_MULTIPLIERS = {
15
+ "simple factual question": 0.05,
16
+ "mathematical calculation": 0.10,
17
+ "creative content generation": 0.80,
18
+ "complex research query": 1.50
19
  }
20
 
21
  def calculate_impact(category, confidence_score, query_text, model_name):
 
22
  model_cost = MODELS.get(model_name, MODELS["Mistral Large 3"])
23
  cat_mult = CATEGORY_MULTIPLIERS.get(category, 0.05)
24
 
 
25
  if confidence_score > 0.8:
26
  conf_mult = 1.0
27
  elif 0.5 <= confidence_score <= 0.8:
 
29
  else:
30
  conf_mult = 1.5
31
 
 
32
  word_count = len(query_text.split())
33
  if word_count < 20:
34
  len_factor = 1.0
 
37
  else:
38
  len_factor = 1.5
39
 
 
40
  water_ml = model_cost["water_ml"] * cat_mult * conf_mult * len_factor
41
  energy_wh = model_cost["energy_wh"] * cat_mult * conf_mult * len_factor
42
+ co2_g = model_cost["co2_g"] * cat_mult * conf_mult * len_factor
43
 
 
44
  water_l = water_ml / 1000.0
45
  energy_kwh = energy_wh / 1000.0
46
 
 
48
  "water_l": round(water_l, 4),
49
  "energy_kwh": round(energy_kwh, 5),
50
  "water_ml": round(water_ml, 1),
51
+ "energy_wh": round(energy_wh, 2),
52
+ "co2_g": round(co2_g, 2)
53
  }