collaborative-decoding / src /ui /interface_builder.py
Alon Albalak
major update: data saved to hf, user sessions maintain separation, fixed scoring bug
36d5e94
"""Main Gradio interface builder"""
import gradio as gr
from ..config.settings import CSS_PATHS
class InterfaceBuilder:
"""Handles creation of the main Gradio interface"""
def __init__(self, app, page_handlers):
self.app = app
self.page_handlers = page_handlers
def create_interface(self):
with gr.Blocks(title="Help the LLM be Creative!", css_paths=CSS_PATHS) as demo:
# Navigation state management
current_page = gr.State("welcome")
has_results = gr.State(False)
# Session and data state management
current_prompt = gr.State(None)
session_id = gr.State(None)
session_data = gr.State({})
# Navigation bar
nav_container = gr.Column(elem_classes=["nav-container"])
with nav_container:
with gr.Row():
nav_welcome = gr.Button("🏠 Welcome", elem_classes=["nav-button", "active"])
nav_tutorial = gr.Button("🎯 Tutorial", elem_classes=["nav-button"])
nav_creative = gr.Button("✨ Be Creative", elem_classes=["nav-button"])
nav_gallery = gr.Button("πŸ† Gallery", elem_classes=["nav-button"])
nav_results = gr.Button("πŸŽ‰ Results", elem_classes=["nav-button"], visible=False)
nav_session = gr.Button("πŸ“Š Progress", elem_classes=["nav-button"], visible=False)
# Landing page
landing_page = gr.Column(visible=True)
with landing_page:
with gr.Column(elem_classes=["landing-page"]):
gr.Markdown("# πŸ€– I'm a poor, boring language model...😒", elem_classes=["landing-title"])
gr.Markdown("# Please help me be creative!", elem_classes=["landing-subtitle"])
with gr.Row():
with gr.Column(scale=1):
tutorial_btn = gr.Button("🎯 Take a Quick Tour", elem_classes=["primary-button"])
with gr.Column(scale=1):
skip_to_game_btn = gr.Button("πŸš€ Let's Be Creative Together!", elem_classes=["secondary-button"])
with gr.Row():
with gr.Column(scale=1):
gallery_btn = gr.Button("πŸ† View Creative Gallery", elem_classes=["secondary-button"])
# Tutorial page
tutorial_page = gr.Column(visible=False)
with tutorial_page:
# Tutorial header
with gr.Column(elem_classes=["tutorial-container"]):
with gr.Column(elem_classes=["tutorial-header"]):
gr.Markdown("# 🎯 Quick Tour", elem_classes=["tutorial-title"])
gr.Markdown("## Here's how you can help me be more creative", elem_classes=["tutorial-subtitle"])
# Current step explanation
tutorial_step_explanation = gr.Markdown("", elem_classes=["tutorial-step-explanation"])
# Tutorial interface components (identical to game interface)
tutorial_prompt_display = gr.Textbox(
label="🎯 Here's what I was asked to write about:",
lines=3,
interactive=False,
elem_classes=["prompt-box"],
visible=False
)
tutorial_partial_response_display = gr.Textbox(
label="πŸ€– Here's the boring start of my response... (help me continue creatively!)",
lines=5,
interactive=False,
elem_classes=["assistant-box"],
visible=False
)
tutorial_input_section = gr.Column(visible=False)
with tutorial_input_section:
with gr.Row():
with gr.Column(scale=4):
tutorial_user_input = gr.Textbox(
label="✨ Continue where my response above leaves off, and then I'll finish the response after you (max 5 tokens)",
placeholder="Type a few words to inspire me...",
lines=2,
elem_classes=["user-box"],
interactive=False
)
with gr.Column(scale=1, min_width=150):
tutorial_token_counter = gr.Markdown(
"**Tokens:** 0/5",
elem_classes=["token-counter"]
)
tutorial_submit_btn = gr.Button("✨ Help Me Be Creative!", variant="primary", interactive=False)
# Tutorial results section
tutorial_results_section = gr.Column(visible=False)
with tutorial_results_section:
tutorial_score_display = gr.HTML()
gr.Markdown("## πŸ”„ See The Difference!")
with gr.Row(equal_height=True):
with gr.Column(scale=1):
gr.Markdown("### πŸ€– My Original Boring Version")
tutorial_original_response = gr.Textbox(
label="What I wrote originally (so boring! 😴)",
lines=6,
interactive=False,
elem_classes=["original-response"]
)
with gr.Column(scale=1):
gr.Markdown("### ✨ Your Creative Version")
tutorial_creative_response = gr.Textbox(
label="What I wrote after your creative input! 🎨",
lines=6,
interactive=False,
elem_classes=["results-box"]
)
# Tutorial navigation
with gr.Column(elem_classes=["tutorial-navigation"]):
with gr.Row():
tutorial_prev_btn = gr.Button("⬅️ Previous", elem_classes=["tutorial-button", "secondary"], visible=False)
tutorial_next_btn = gr.Button("Next ➑️", elem_classes=["tutorial-button"])
tutorial_play_btn = gr.Button("πŸš€ Let's Play!", elem_classes=["tutorial-button"], visible=False)
tutorial_back_btn = gr.Button("⬅️ Back to Start", elem_classes=["tutorial-button", "secondary"])
# Gallery page
gallery_page = gr.Column(visible=False)
with gallery_page:
with gr.Column(elem_classes=["gallery-container"]):
gr.Markdown("# πŸ† Creative Response Gallery", elem_classes=["tutorial-title"])
gr.Markdown("### Showcasing the most creative responses from our community!", elem_classes=["tutorial-subtitle"])
gallery_filter = gr.Dropdown(
choices=[("All Creative (β‰₯0.15)", 0.15), ("Great (β‰₯0.3)", 0.3), ("Exceptional (β‰₯0.5)", 0.5), ("Legendary (β‰₯0.7)", 0.7)],
value=0.3,
label="🎯 Filter by creativity level:",
elem_classes=["prompt-box"]
)
gallery_display = gr.HTML()
with gr.Row():
gallery_refresh_btn = gr.Button("πŸ”„ Refresh Gallery", elem_classes=["tutorial-button"])
gallery_back_btn = gr.Button("⬅️ Back to Start", elem_classes=["tutorial-button", "secondary"])
# Creative page components (initially hidden)
creative_page = gr.Column(visible=False)
with creative_page:
prompt_display = gr.Textbox(
label="🎯 Here's what I was asked to do:",
lines=3,
interactive=False,
elem_classes=["prompt-box"]
)
partial_response_display = gr.Textbox(
label="πŸ€– Here's the boring start of my response... (help me continue creatively!)",
lines=5,
interactive=False,
elem_classes=["assistant-box"]
)
user_input = gr.Textbox(
label="✨ Continue where my response above leaves off, and then I'll finish the response after you (max 5 tokens)",
placeholder="Type a few words to continue my response...",
lines=1, # changed to single line so that "enter" submits the request
elem_classes=["user-box"]
)
token_counter = gr.Markdown(
"**Tokens:** 0/5",
elem_classes=["token-counter"]
)
token_visualization = gr.Markdown(
"Once you start typing, I'll show you what you've written, but in tokens!",
elem_classes=["token-visualization"]
)
submit_btn = gr.Button("✨ Help Me Be Creative!", variant="primary", size="lg")
# Loading and error messages (positioned right after submit button)
loading_message = gr.Textbox(visible=False, interactive=False, label="πŸ€– I'm thinking...", value="Working on your creative response... This is exciting!")
error_message = gr.Textbox(visible=False, interactive=False, label="πŸ™‹ Oops!", elem_classes=["error-box"])
# Clear section divider
gr.HTML("<hr style='margin: 40px 0; border: none; height: 2px; background: linear-gradient(to right, transparent, #667eea, transparent); opacity: 0.3;'>")
# Help and inspiration section
gr.Markdown("### πŸ“š Need Help or Inspiration?", elem_classes=["section-header"])
# Inspire Me section - collapsible
gr.Markdown("✨ **Need some inspiration?** See how others have creatively continued this same prompt!", elem_classes=["token-explanation"])
with gr.Accordion("πŸ’‘ Inspire Me! Show creative examples from others", open=False):
inspire_me_btn = gr.Button("🎲 Show Different Examples", elem_classes=["inspire-me-button"])
inspire_me_display = gr.HTML()
gr.Markdown(
"πŸ’‘ **About tokens:** Your input is measured in \"tokens\" (how I process text). About 5 tokens β‰ˆ 3-5 words. You'll see your exact count above as you type!",
elem_classes=["token-explanation"]
)
# Session progress page
session_page = gr.Column(visible=False)
with session_page:
gr.HTML("""
<div class="results-header">
<h1>πŸ“Š Your Creative Journey</h1>
<p>Track your progress, achievements, and creative evolution!</p>
</div>
""")
# Main session stats display
session_main_stats = gr.HTML()
# Detailed session history
gr.Markdown("## πŸ“ˆ Your Creative History", elem_classes=["results-section-header"])
session_history_display = gr.HTML()
# Session achievements
gr.Markdown("## πŸ† Your Achievements", elem_classes=["results-section-header"])
session_achievements_display = gr.HTML()
# Back to creative action
with gr.Column(elem_classes=["action-section"]):
gr.Markdown("### Ready to add to your journey?")
back_to_creative_btn = gr.Button("✨ Continue Being Creative!", elem_classes=["primary-button"])
# Results page components
results_page = gr.Column(visible=False)
with results_page:
cosine_distance_display = gr.HTML(visible=False)
results_prompt_display = gr.Textbox(
label="🎯 Here's what I was asked to write about:",
lines=2,
interactive=False,
elem_classes=["prompt-box"]
)
results_partial_display = gr.Textbox(
label="πŸ€– My Boring Start",
lines=3,
interactive=False,
elem_classes=["assistant-box"]
)
results_user_input_display = gr.Textbox(
label="✨ Your Creative Input",
lines=2,
interactive=False,
elem_classes=["user-box"]
)
# Side-by-side comparison
gr.Markdown("## πŸ”„ See The Difference!")
with gr.Row(equal_height=True):
with gr.Column(scale=1):
gr.Markdown("### πŸ€– My Original Boring Version")
original_response_display = gr.Textbox(
label="What I wrote originally (so boring! 😴)",
lines=8,
interactive=False,
elem_classes=["original-response"]
)
with gr.Column(scale=1):
gr.Markdown("### ✨ Your Creative Version")
results_continuation_display = gr.Textbox(
label="What I wrote after your creative input! 🎨",
lines=8,
interactive=False,
elem_classes=["results-box"]
)
# Visualization
gr.Markdown("## πŸ“Š How does your help compare with others?")
violin_plot_display = gr.Plot(
label="Score Distribution by Token Count",
visible=False
)
# Collapsible explanation of cosine similarity
with gr.Accordion("πŸ€” How does the scoring work? (Click to learn!)", open=False):
gr.Markdown("""
## The Magic Behind Your Creativity Score! ✨
I use something called **"Cosine Distance"** to see how different your version is from my boring original!
**Think of it like this:** 🎯
- I convert both responses into "meaning fingerprints"
- Then I measure how far apart these fingerprints are
- **Farther apart = More creative = Higher score!** πŸ†
**Score Guide:**
- **0.7+** = 🌟 WOW! You completely changed my direction!
- **0.5+** = πŸ”₯ Amazing! Very different from my original
- **0.3+** = ✨ Great job! Nice creative twist
- **0.15+** = πŸ’‘ Good start! I can see the difference
- **Below 0.15** = πŸ€” Pretty similar to what I would have written anyway
**Pro tip:** The most unexpected words often lead to the highest scores! πŸŽͺ
""")
with gr.Row():
try_again_same_btn = gr.Button("πŸ”„ Let Me Try Again!", elem_classes=["try-again-button"])
try_again_new_btn = gr.Button("🎯 Give Me A New Challenge!", elem_classes=["try-again-button"])
# Tutorial state
tutorial_current_step = gr.State(1)
# Wire up all the event handlers
self._wire_event_handlers(
# Navigation components
nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session,
# Landing page components
tutorial_btn, skip_to_game_btn, gallery_btn,
# Tutorial components
tutorial_next_btn, tutorial_prev_btn, tutorial_play_btn, tutorial_back_btn,
# Gallery components
gallery_filter, gallery_refresh_btn, gallery_back_btn,
# Creative page components
user_input, submit_btn, inspire_me_btn,
# Results page components
try_again_same_btn, try_again_new_btn,
# Session page components
back_to_creative_btn,
# State components
current_page, has_results, tutorial_current_step,
# Display components
landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page,
prompt_display, partial_response_display, token_counter, token_visualization,
loading_message, error_message, inspire_me_display, gallery_display,
cosine_distance_display, results_prompt_display, results_partial_display,
results_user_input_display, original_response_display, results_continuation_display,
violin_plot_display, session_main_stats, session_history_display, session_achievements_display,
# Tutorial display components
tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
tutorial_input_section, tutorial_results_section, tutorial_user_input, tutorial_token_counter,
tutorial_score_display, tutorial_original_response, tutorial_creative_response,
tutorial_prev_btn, tutorial_next_btn, tutorial_play_btn,
# State variables
current_prompt, session_id, session_data
)
# Demo starts with landing page visible
return demo
def _wire_event_handlers(self, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session,
tutorial_btn, skip_to_game_btn, gallery_btn, tutorial_next_btn, tutorial_prev_btn,
tutorial_play_btn, tutorial_back_btn, gallery_filter, gallery_refresh_btn, gallery_back_btn,
user_input, submit_btn, inspire_me_btn, try_again_same_btn, try_again_new_btn,
back_to_creative_btn, current_page, has_results, tutorial_current_step,
landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page,
prompt_display, partial_response_display, token_counter, token_visualization,
loading_message, error_message, inspire_me_display, gallery_display,
cosine_distance_display, results_prompt_display, results_partial_display,
results_user_input_display, original_response_display, results_continuation_display,
violin_plot_display, session_main_stats, session_history_display, session_achievements_display,
tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
tutorial_input_section, tutorial_results_section, tutorial_user_input, tutorial_token_counter,
tutorial_score_display, tutorial_original_response, tutorial_creative_response,
tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref,
current_prompt, session_id, session_data):
"""Wire up all the event handlers for the interface"""
# Wire up landing page buttons
tutorial_btn.click(
self.page_handlers.show_tutorial,
outputs=[
current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session,
tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
tutorial_input_section, tutorial_results_section,
tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref,
tutorial_current_step
]
)
skip_to_game_btn.click(
self.page_handlers.start_game,
inputs=[current_prompt, session_id],
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, inspire_me_display, current_prompt, session_id]
)
# Wire up gallery button (handled at end of function)
# Wire up gallery page buttons
gallery_back_btn.click(
self.page_handlers.back_to_landing,
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display]
)
gallery_refresh_btn.click(
self.page_handlers.show_gallery,
inputs=[gallery_filter],
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display]
)
gallery_filter.change(
self.page_handlers.show_gallery,
inputs=[gallery_filter],
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display]
)
# Wire up tutorial page buttons
tutorial_back_btn.click(
lambda: self.page_handlers.switch_to_page("welcome"),
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
tutorial_play_btn.click(
self.page_handlers.load_creative_with_prompt,
inputs=[current_prompt, session_id],
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, inspire_me_display, current_prompt, session_id],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
# Wire up tutorial navigation
tutorial_next_btn.click(
self.page_handlers.tutorial_next_step,
inputs=[tutorial_current_step],
outputs=[
tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
tutorial_input_section, tutorial_user_input, tutorial_token_counter,
tutorial_results_section, tutorial_original_response, tutorial_creative_response, tutorial_score_display,
tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref,
tutorial_current_step
]
)
tutorial_prev_btn.click(
self.page_handlers.tutorial_prev_step,
inputs=[tutorial_current_step],
outputs=[
tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
tutorial_input_section, tutorial_results_section,
tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref,
tutorial_current_step
]
)
submit_btn.click(
self.page_handlers.on_submit_start,
inputs=[user_input],
outputs=[submit_btn, loading_message, error_message, landing_page, gallery_page, creative_page, results_page, cosine_distance_display]
).then(
self.page_handlers.on_submit_process,
inputs=[user_input, current_prompt, session_id],
outputs=[submit_btn, loading_message, error_message, landing_page, gallery_page, creative_page, results_page, cosine_distance_display, results_prompt_display, results_partial_display, results_user_input_display, original_response_display, results_continuation_display, violin_plot_display, current_page, has_results, nav_results, nav_session, nav_welcome, nav_tutorial, nav_creative, nav_gallery]
)
user_input.submit(
self.page_handlers.on_submit_start,
inputs=[user_input],
outputs=[submit_btn, loading_message, error_message, landing_page, gallery_page, creative_page, results_page, cosine_distance_display]
).then(
self.page_handlers.on_submit_process,
inputs=[user_input, current_prompt, session_id],
outputs=[submit_btn, loading_message, error_message, landing_page, gallery_page, creative_page, results_page, cosine_distance_display, results_prompt_display, results_partial_display, results_user_input_display, original_response_display, results_continuation_display, violin_plot_display, current_page, has_results, nav_results, nav_session, nav_welcome, nav_tutorial, nav_creative, nav_gallery]
)
# Try again button - stay on same prompt, switch to creative page
try_again_same_btn.click(
self.page_handlers.try_same_prompt,
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, submit_btn, user_input, loading_message, error_message, token_counter, token_visualization],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
# Try new prompt button - new prompt and switch to creative page
try_again_new_btn.click(
self.page_handlers.try_new_prompt,
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, submit_btn, user_input, loading_message, error_message, inspire_me_display, current_prompt],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
# Wire up inspire me button
inspire_me_btn.click(
self.page_handlers.show_inspire_me_examples,
inputs=[current_prompt],
outputs=[inspire_me_display]
)
# Wire up real-time token counter
user_input.change(
self.page_handlers.update_token_count,
inputs=[user_input],
outputs=[token_counter, token_visualization]
)
# Navigation button handlers
nav_welcome.click(
lambda: self.page_handlers.switch_to_page("welcome"),
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
nav_tutorial.click(
self.page_handlers.load_tutorial_with_content,
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display, tutorial_input_section, tutorial_results_section, tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref, tutorial_current_step],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
nav_creative.click(
self.page_handlers.load_creative_with_prompt,
inputs=[current_prompt, session_id],
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, inspire_me_display, current_prompt, session_id],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
nav_gallery.click(
self.page_handlers.load_gallery_with_content,
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
nav_results.click(
lambda: self.page_handlers.switch_to_page("results"),
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
# Update session page when navigating to it
nav_session.click(
self.page_handlers.navigate_to_session,
inputs=[session_id],
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, session_main_stats, session_history_display, session_achievements_display],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
# Update back to creative button in session page
back_to_creative_btn.click(
lambda: self.page_handlers.switch_to_page("creative"),
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)
# Wire up gallery button from landing page
gallery_btn.click(
self.page_handlers.load_gallery_with_content,
outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display],
js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
)