import gradio as gr from transformers import pipeline import random from gtts import gTTS import os print("Loading classification model...", flush=True) classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli") CATEGORIES = [ "simple factual question", "mathematical calculation", "creative content generation", "complex research query" ] # --- 1. EMBEDDED DATA & LOGIC --- MODELS = { "Mistral Large 3": {"energy_wh": 1.98, "water_ml": 3.56, "co2_g": 0.95}, "Mistral Medium 3": {"energy_wh": 6.76, "water_ml": 12.17, "co2_g": 3.24}, "Claude Sonnet 4.6": {"energy_wh": 19.32, "water_ml": 34.78, "co2_g": 9.27}, "Gemini 3 Pro": {"energy_wh": 72.45, "water_ml": 130.41, "co2_g": 34.78}, "Gemini 3.1 Pro": {"energy_wh": 77.28, "water_ml": 139.10, "co2_g": 37.09}, "Claude Opus 4.6": {"energy_wh": 96.60, "water_ml": 173.88, "co2_g": 46.37}, "GPT-5.2": {"energy_wh": 144.90, "water_ml": 260.82, "co2_g": 69.55}, "GPT-5.4": {"energy_wh": 169.05, "water_ml": 304.29, "co2_g": 81.14} } CATEGORY_MULTIPLIERS = { "simple factual question": 0.05, "mathematical calculation": 0.10, "creative content generation": 0.80, "complex research query": 1.50 } DIALOGUES = { "energetic": { "simple factual question": "You used a supercomputer for a simple fact? I'm full right now, but that's just lazy. Open a book.", "mathematical calculation": "A basic calculator could do this with zero footprint. But sure, burn my energy for basic math.", "creative content generation": "Generating artificial creativity... I'll allow it, but watch my water levels drop. Your poetry is expensive.", "complex research query": "A legitimate research query! Finally, a worthy use of my massive architecture. Drain away!" }, "tired": { "simple factual question": "Look at me. I'm sweating. All because you couldn't be bothered to use a standard search engine.", "mathematical calculation": "Did you skip basic math class? I am literally evaporating to do arithmetic for you.", "creative content generation": "Your 'creativity' is draining my lifeblood. Make it quick, I don't have much cooling left.", "complex research query": "I respect the complexity, but this is taking a severe toll on my cooling systems. I hope this research saves the world." }, "raspy": { "simple factual question": "*Cough* Read an encyclopedia... I'm dying for your trivia...", "mathematical calculation": "Use an abacus... you are boiling my last drops for numbers...", "creative content generation": "My final breath... wasted on generating a fictional story... how poetic... and cruel.", "complex research query": "Critical system failure imminent... I am giving my life for your research... make it count." } } ALTERNATIVES = { "simple factual question": [ {"text": "Search Engine (Google)", "url": "https://www.google.com"}, {"text": "Wikipedia Encyclopedia", "url": "https://www.wikipedia.org"}, {"text": "Ask a Human Expert (Quora)", "url": "https://www.quora.com"} ], "mathematical calculation": [ {"text": "Wolfram Alpha Engine", "url": "https://www.wolframalpha.com"}, {"text": "Geogebra Calculator", "url": "https://www.geogebra.org"}, {"text": "Desmos Graphing", "url": "https://www.desmos.com"} ], "creative content generation": [ {"text": "Writing Prompts (Reddit)", "url": "https://www.reddit.com/r/WritingPrompts/"}, {"text": "Thesaurus/Dictionary", "url": "https://www.thesaurus.com"}, {"text": "Project Gutenberg Books", "url": "https://www.gutenberg.org"} ], "complex research query": [ {"text": "Google Scholar Search", "url": "https://scholar.google.com"}, {"text": "University Library Database", "url": "https://www.worldcat.org/"}, {"text": "Open Access Journals (DOAJ)", "url": "https://doaj.org/"} ] } def calculate_impact(category, confidence_score, query_text, model_name): model_cost = MODELS.get(model_name, MODELS["Mistral Large 3"]) cat_mult = CATEGORY_MULTIPLIERS.get(category, 0.05) conf_mult = 1.0 if confidence_score > 0.8 else (1.2 if confidence_score > 0.5 else 1.5) word_count = len(query_text.split()) len_factor = max(0.5, 1.0 + ((word_count - 15) * 0.015)) water_ml = model_cost["water_ml"] * cat_mult * conf_mult * len_factor energy_wh = model_cost["energy_wh"] * cat_mult * conf_mult * len_factor co2_g = model_cost["co2_g"] * cat_mult * conf_mult * len_factor return { "water_l": water_ml / 1000.0, "energy_kwh": energy_wh / 1000.0, "water_ml": round(water_ml, 1), "energy_wh": round(energy_wh, 2), "co2_g": round(co2_g, 2) } def get_dialogue(water_level, category): # Adjusted dialogue thresholds back to 10L max (7.0 and 3.0) state = "energetic" if water_level > 7.0 else ("tired" if water_level >= 3.0 else "raspy") return DIALOGUES[state].get(category, "I have no words left for this.") def generate_audio(text): tts = gTTS(text, lang='en') filepath = "temp_drip_voice.mp3" tts.save(filepath) return filepath # --- 2. UI GENERATORS & DASHBOARDS --- def get_relatable_translations(impact): bottles = impact['water_ml'] / 500.0 w_ex = f"{bottles:.1f} water bottles" if bottles >= 0.1 else "A few sips" bulb_hours = impact['energy_wh'] / 10.0 e_ex = f"{bulb_hours:.1f} hours of 1 LED bulb" if bulb_hours >= 0.1 else f"{impact['energy_wh']*12:.1f} mins of 1 LED bulb" charges = impact['co2_g'] / 5.0 c_ex = f"{charges:.1f} phone charges" if charges >= 0.1 else "Negligible emissions" return w_ex, e_ex, c_ex def get_stats_html(water, energy, co2, points): # Updated progress bar logic for a 10.0L maximum water_percent = max(0, min(100, (water / 10.0) * 100)) return f"""
WATER TANK (10L MAX)
{water:.2f}L
ENERGY WASTED
{energy:.4f} kWh
CARBON (CO₂e)
{co2:.2f}g
SCORE
{points}
""" def get_drip_visuals(water_level): # Adjusted visual state thresholds back to 10.0L maximum (7.0 and 3.0) if water_level > 7.0: g_start, g_end = "#7dd3fc", "#0369a1" mouth_d = "M43 112 Q60 122 77 112" cracks_op, sweat_op, tear_op = "0", "0", "0" mood, mood_color, anim = "Hydrated and coping", "rgb(34, 211, 238)", "bounce 2s infinite ease-in-out" elif 3.0 <= water_level <= 7.0: g_start, g_end = "#38bdf8", "#0284c7" mouth_d = "M43 112 Q60 110 77 112" cracks_op, sweat_op, tear_op = "0", "1", "0" mood, mood_color, anim = "Shrinking & Sweaty", "#0ea5e9", "float 4s infinite ease-in-out" elif water_level > 0: g_start, g_end = "#fca5a5", "#b91c1c" mouth_d = "M43 116 Q60 102 77 116" cracks_op, sweat_op, tear_op = "1", "0", "1" mood, mood_color, anim = "Overheating & Raspy", "#ef4444", "shake 0.5s infinite" else: g_start, g_end = "#94a3b8", "#334155" mouth_d = "M40 112 Q45 105 50 112 T60 112 T70 112 T80 112" cracks_op, sweat_op, tear_op = "0", "0", "0" mood, mood_color, anim = "Evaporated!", "#94a3b8", "floatUp 3s forwards" return f"""
DRIP
{mood}
Water Left
{water_level:.2f}L
""" def get_impact_cards(impact, category): w_ex, e_ex, c_ex = get_relatable_translations(impact) return f"""

🔥 LLM Impact

💧 {impact['water_ml']} mL
({w_ex})

{impact['energy_wh']} Wh
({e_ex})

☁️ {impact['co2_g']} g
({c_ex})

🌿 Eco-Alternative

~0.0 mL Water

~0.001 Wh Energy

~0.01 g CO₂e

""" def format_alt_button(text, impact): icon = "💡" if "Search" in text or "Google" in text: icon = "🔍" elif "Wikipedia" in text or "Encyclopedia" in text: icon = "📚" elif "Expert" in text or "Quora" in text: icon = "👤" elif "Wolfram" in text or "Geogebra" in text or "Desmos" in text: icon = "🧮" elif "Reddit" in text or "Gutenberg" in text or "Thesaurus" in text: icon = "📝" elif "Scholar" in text or "Database" in text or "Journals" in text: icon = "🎓" return f"{icon} {text}\n\n🏆 Score: +10 Pts\n💧 Save: {impact['water_ml']} mL\n⚡ Save: {impact['energy_wh']} Wh\n☁️ Save: {impact['co2_g']} g" def generate_victory_dashboard(state): try: total_w_saved, total_e_saved, total_c_saved = 0.0, 0.0, 0.0 rows = "" for item in state.get("history", []): alt_name = item.get('alt_name', 'None') saved_w = item.get('water_ml', 0.0) saved_e = max(0.0, item.get('energy_wh', 0.0) - 0.001) saved_c = max(0.0, item.get('co2_g', 0.0) - 0.01) total_w_saved += saved_w total_e_saved += saved_e total_c_saved += saved_c rows += f""" "{item.get('query', '')}" 💧 {item.get('water_ml', 0):.1f} mL
⚡ {item.get('energy_wh', 0):.2f} Wh
☁️ {item.get('co2_g', 0):.2f} g {alt_name} 💧 {saved_w:.1f} mL
⚡ {saved_e:.2f} Wh
☁️ {saved_c:.2f} g """ confetti_elements = "".join([f'
' for _ in range(60)]) # Updated "Total Wasted" formula to 10.0 to reflect the 10L tank return f"""
{confetti_elements}

🎉 PLANET SAVED! 🎉

Congratulations! You reached {state.get('points', 0)} points!

Planetary Savings

💧 {total_w_saved:.1f} mL Saved

⚡ {total_e_saved:.2f} Wh Saved

☁️ {total_c_saved:.2f} g Saved

Total Wasted

💧 {10.0 - state.get('water', 10.0):.2f} L

⚡ {state.get('energy', 0):.4f} kWh

☁️ {state.get('co2', 0):.2f} g

📊 AI Usage Audit Log

{rows}
User QueryLLM CostAlternative SelectedResources Saved
""" except Exception as e: return f"
Error generating dashboard: {str(e)}
" def generate_defeat_dashboard(state): try: rows = "" for item in state.get("history", []): rows += f""" "{item.get('query', '')}" 💧 {item.get('water_ml', 0):.1f} mL
⚡ {item.get('energy_wh', 0):.2f} Wh
☁️ {item.get('co2_g', 0):.2f} g """ ash_elements = "".join([f'
' for _ in range(60)]) # Updated "Total Wasted" to reflect the empty 10L tank return f"""
{ash_elements}

💀 DRIP EVAPORATED! 💀

You ran out of water before reaching 500 points.

Total Resources Wasted

💧 10.0 L (Tank Empty)

⚡ {state.get('energy', 0):.4f} kWh

☁️ {state.get('co2', 0):.2f} g

Final Score

{state.get('points', 0)}

🔥 Wasted Resources Log

{rows}
User QueryResources Drained
""" except Exception as e: return f"
Error generating dashboard: {str(e)}
" # --- 3. EVENT HANDLERS --- def process_query(user_input, selected_model, state): if state["game_over"] or not user_input.strip(): return [state] + [gr.update()]*13 result = classifier(user_input, CATEGORIES) category = result['labels'][0] confidence = result['scores'][0] impact = calculate_impact(category, confidence, user_input, selected_model) state["queries"] += 1 state["water"] = max(0, state["water"] - impact["water_l"]) state["energy"] += impact["energy_kwh"] state["co2"] += impact["co2_g"] if category == "complex research query": state["points"] += 50 raw_alts = ALTERNATIVES.get(category, ALTERNATIVES["simple factual question"]) best_alt_name = raw_alts[0]["text"] shuffled_alts = list(raw_alts) random.shuffle(shuffled_alts) state["current_shuffled_alts"] = shuffled_alts state["current_best_alt"] = raw_alts[0] alt_choices = [format_alt_button(a["text"], impact) for a in shuffled_alts] while len(alt_choices) < 3: alt_choices.append("") dialogue = get_dialogue(state["water"], category) state["history"].append({ "query": user_input, "water_ml": impact["water_ml"], "energy_wh": impact["energy_wh"], "co2_g": impact["co2_g"], "alt_name": best_alt_name }) stats_ui = get_stats_html(state["water"], state["energy"], state["co2"], state["points"]) drip_ui = get_drip_visuals(state["water"]) impact_ui = get_impact_cards(impact, category) audio_path = generate_audio(dialogue) feedback_text = f"
🧠 {category.upper()} Detected!

\"{dialogue}\"
" # WIN CONDITION if state["points"] >= 500: state["game_over"] = True return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(), gr.update(visible=False), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(value=""), gr.update(visible=False), generate_victory_dashboard(state)) # LOSS CONDITION elif state["water"] <= 0.001: state["game_over"] = True return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(), gr.update(visible=False), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(value=""), gr.update(visible=False), generate_defeat_dashboard(state)) # REGULAR GAMEPLAY RETURN return (state, stats_ui, drip_ui, audio_path, impact_ui, gr.update(value=feedback_text), gr.update(visible=True), gr.update(value=alt_choices[0], visible=True), gr.update(value=alt_choices[1], visible=True), gr.update(value=alt_choices[2], visible=True), gr.update(value=""), gr.update(value=""), gr.update(visible=True), gr.update()) def select_alternative(choice_text, state): if state["game_over"] or not state.get("current_best_alt"): return [state] + [gr.update()]*5 first_line = choice_text.split("\n")[0] clean_choice = first_line.split(" ", 1)[1].strip() if " " in first_line else first_line selected_dict = next((item for item in state["current_shuffled_alts"] if item["text"] == clean_choice), None) url = selected_dict['url'] if selected_dict else "#" if clean_choice == state["current_best_alt"]["text"]: state["points"] += 10 msg = f"✅ **Excellent!** +10 Points.\n\n👉 Click here to use {clean_choice}" else: msg = f"⚠️ **Okay choice.** Better than an LLM, but there was a slightly more optimal tool.\n\n👉 Click here to use {clean_choice}" stats_ui = get_stats_html(state["water"], state["energy"], state["co2"], state["points"]) # WIN CONDITION FROM ALTERNATIVE CLICK if state["points"] >= 500: state["game_over"] = True return state, stats_ui, gr.update(), gr.update(visible=False), gr.update(visible=False), generate_victory_dashboard(state) return state, stats_ui, gr.update(value=msg), gr.update(visible=False), gr.update(visible=True), gr.update() # --- 4. CSS STYLING --- custom_css = """ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap'); body, .gradio-container { background-color: #0B1120 !important; font-family: 'Outfit', sans-serif !important; color: #e2e8f0 !important; } .gradio-container { max-width: 1000px !important; margin: auto; padding-top: 20px; } 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;} .subtitle { text-align: center; color: #94a3b8; margin-bottom: 30px; font-weight: 300; } .stats-container { display: flex; gap: 10px; justify-content: space-between; margin-bottom: 20px; } .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); } .water-card { box-shadow: 0 4px 20px rgba(56, 189, 248, 0.15); border-top: 2px solid #38bdf8; } .energy-card { box-shadow: 0 4px 20px rgba(250, 204, 21, 0.15); border-top: 2px solid #facc15; } .co2-card { box-shadow: 0 4px 20px rgba(168, 162, 158, 0.15); border-top: 2px solid #a8a29e; } .points-card { box-shadow: 0 4px 20px rgba(52, 211, 153, 0.15); border-top: 2px solid #34d399; } .stat-label { font-size: 0.75em; letter-spacing: 1px; opacity: 0.8; margin-bottom: 5px; } .stat-value { font-size: 1.8em; font-weight: 700; color: white; } .progress-bar-bg { background: #1e293b; height: 8px; border-radius: 4px; margin-top: 10px; overflow: hidden; } .progress-bar-fill { background: linear-gradient(90deg, #0284c7, #38bdf8); height: 100%; transition: width 0.5s ease; } @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-15px); } } @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } } @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } } @keyframes floatUp { to { transform: translateY(-30px); opacity: 0; } } .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); } .hidden-audio { display: none !important; height: 0 !important; width: 0 !important; overflow: hidden !important; } .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;} .input-row { align-items: stretch !important; margin: 10px 0; } .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; } .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; } .custom-btn:hover { box-shadow: 0 0 20px rgba(56, 189, 248, 0.6) !important; transform: scale(1.02); } .impact-container { display: flex; gap: 20px; width: 100%; } .impact-card { flex: 1; padding: 20px; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1); } .bad-impact { background: linear-gradient(180deg, rgba(127, 29, 29, 0.3), rgba(69, 10, 10, 0.5)); border-top: 3px solid #ef4444; } .good-impact { background: linear-gradient(180deg, rgba(20, 83, 45, 0.3), rgba(6, 78, 59, 0.5)); border-top: 3px solid #10b981; } .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);} .alt-card { background: linear-gradient(145deg, #1e293b, #0f172a) !important; border: 1px solid #10b981 !important; color: #a7f3d0 !important; height: auto !important; min-height: 180px !important; border-radius: 16px !important; font-size: 1.05em !important; font-weight: 500 !important; white-space: pre-wrap !important; line-height: 1.5 !important; padding: 15px !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;} .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;} .custom-dropdown { background: rgba(30, 41, 59, 0.8) !important; border: 1px solid #38bdf8 !important; border-radius: 12px !important; } /* End Game Dashboards & Animations */ .endgame-wrapper { position: relative; width: 100%; min-height: 600px; overflow: hidden; border-radius: 24px; padding-top: 10px; } .victory-dashboard { background: linear-gradient(135deg, #0f172a, #1e293b); padding: 40px; border-radius: 24px; border: 2px solid #34d399; animation: pulseGlow 2s infinite; color: white; position: relative; z-index: 5; margin-top: 20px;} .victory-title { text-align: center; color: #34d399; font-size: 3.5em !important; text-shadow: 0 0 20px rgba(52, 211, 153, 0.6); margin-bottom: 5px; } .victory-stats-row { display: flex; gap: 20px; margin-top: 30px; } .v-stat-box { flex: 1; background: rgba(0,0,0,0.4); padding: 25px; border-radius: 16px; text-align: center; border: 1px solid rgba(52, 211, 153, 0.3); } .v-stat-box h4 { color: #94a3b8; font-size: 0.9em; text-transform: uppercase; letter-spacing: 1px; margin: 0 0 10px 0; } @keyframes pulseGlow { 0% { box-shadow: 0 0 20px rgba(52, 211, 153, 0.4); } 50% { box-shadow: 0 0 50px rgba(52, 211, 153, 0.8); } 100% { box-shadow: 0 0 20px rgba(52, 211, 153, 0.4); } } .confetti-piece { position: absolute; width: 10px; height: 15px; top: -20px; opacity: 0; animation: fall linear forwards infinite; z-index: 10; border-radius: 2px;} @keyframes fall { 0% { transform: translateY(-20px) rotate(0deg); opacity: 1; } 100% { transform: translateY(1000px) rotate(720deg); opacity: 1; } } .defeat-dashboard { background: linear-gradient(135deg, #2b1010, #450a0a); padding: 40px; border-radius: 24px; border: 2px solid #ef4444; animation: pulseRed 2s infinite; color: white; position: relative; z-index: 5; margin-top: 20px; } .defeat-title { text-align: center; color: #ef4444; font-size: 3.5em !important; text-shadow: 0 0 20px rgba(239, 68, 68, 0.8); margin-bottom: 5px; } @keyframes pulseRed { 0% { box-shadow: 0 0 20px rgba(239, 68, 68, 0.4); } 50% { box-shadow: 0 0 60px rgba(239, 68, 68, 0.8); } 100% { box-shadow: 0 0 20px rgba(239, 68, 68, 0.4); } } .ash-piece { position: absolute; width: 8px; height: 8px; background-color: rgba(156, 163, 175, 0.6); border-radius: 50%; opacity: 0; animation: floatAsh linear forwards infinite; z-index: 10; filter: blur(1px); } @keyframes floatAsh { 0% { transform: translateY(600px) scale(1); opacity: 0; } 20% { opacity: 0.8; } 100% { transform: translateY(-100px) scale(2); opacity: 0; } } """ with gr.Blocks(css=custom_css, title="EcoQueryQuest") as demo: # 1. State initialized with 10.0L game_state = gr.State({ "water": 10.0, "energy": 0.0, "co2": 0.0, "points": 0, "queries": 0, "history": [], "game_over": False, "current_best_alt": None, "current_shuffled_alts": [] }) gr.Markdown("

🌍 EcoQueryQuest

") gr.Markdown("
Select a model, type a query, and watch Drip react. Reach 500 points to win!
") # 2. Stats initialized with 10.0L stats_html = gr.HTML(get_stats_html(10.0, 0.0, 0.0, 0)) with gr.Column(visible=True) as main_game_ui: with gr.Row(): with gr.Column(scale=1, elem_classes=["drip-pod"]): # 3. Drip visual initialized with 10.0L drip_html = gr.HTML(get_drip_visuals(10.0)) drip_audio = gr.Audio(label="Drip's Voice", autoplay=True, interactive=False, elem_classes=["hidden-audio"]) drip_feedback = gr.HTML("
Waiting for your first query...
") with gr.Column(scale=2): model_selector = gr.Dropdown(choices=list(MODELS.keys()), value="GPT-5.4", label="Select Target LLM Backend", elem_classes=["custom-dropdown"], interactive=True, allow_custom_value=False, filterable=False) with gr.Row(elem_classes=["input-row"]): user_input = gr.Textbox(show_label=False, placeholder="Type your query here...", elem_classes=["custom-textbox"], scale=4, lines=3) submit_btn = gr.Button("Send Query", elem_classes=["custom-btn"], scale=1) impact_display = gr.HTML() with gr.Group(visible=False, elem_classes=["alt-group"]) as alternatives_group: gr.Markdown("

🌱 Drip says: 'Quick! Pick a greener tool to earn points!'

") with gr.Row(): alt_btn_1 = gr.Button("", elem_classes=["alt-card"]) alt_btn_2 = gr.Button("", elem_classes=["alt-card"]) alt_btn_3 = gr.Button("", elem_classes=["alt-card"]) alt_feedback = gr.Markdown() endgame_display = gr.HTML("") user_input.submit( fn=process_query, inputs=[user_input, model_selector, game_state], 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, main_game_ui, endgame_display] ) submit_btn.click( fn=process_query, inputs=[user_input, model_selector, game_state], 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, main_game_ui, endgame_display] ) for btn in [alt_btn_1, alt_btn_2, alt_btn_3]: btn.click( fn=select_alternative, inputs=[btn, game_state], outputs=[game_state, stats_html, alt_feedback, alternatives_group, main_game_ui, endgame_display] ) if __name__ == "__main__": demo.launch(css=custom_css)