Spaces:
Running
Running
import gradio as gr | |
from css import custom_css, loading_css_styles | |
from audio.audio_generator import ( | |
update_audio, | |
cleanup_music_session, | |
) | |
import logging | |
from agent.runner import process_step | |
import uuid | |
from game_constructor import ( | |
SETTING_SUGGESTIONS, | |
CHARACTER_SUGGESTIONS, | |
GENRE_OPTIONS, | |
load_setting_suggestion, | |
load_character_suggestion, | |
start_game_with_settings, | |
) | |
from app_description import app_description | |
CONCURRENCY_LIMIT = 10000 | |
logger = logging.getLogger(__name__) | |
async def return_to_constructor(user_hash: str): | |
"""Return to the constructor and reset user state and audio.""" | |
from agent.redis_state import reset_user_state | |
await reset_user_state(user_hash) | |
await cleanup_music_session(user_hash) | |
return ( | |
gr.update(visible=False), # loading_indicator | |
gr.update(visible=True), # constructor_interface | |
gr.update(visible=False), # game_interface | |
gr.update(visible=False), # error_message | |
) | |
num_visits = 0 | |
async def generate_user_hash(): | |
hash = str(uuid.uuid4()) | |
global num_visits | |
num_visits += 1 | |
logger.info(f"Generated user hash: {hash}, num_visits: {num_visits}") | |
return gr.update(value=hash) | |
async def update_scene(user_hash: str, choice): | |
logger.info(f"Updating scene with choice: {choice}") | |
if not isinstance(choice, str): | |
return gr.update(), gr.update(), gr.update(), gr.update() | |
result = await process_step( | |
user_hash=user_hash, | |
step="choose", | |
choice_text=choice, | |
) | |
if result.get("game_over"): | |
ending = result["ending"] | |
ending_text = ( | |
ending.get("description") or ending.get("condition", "") | |
) + "\n[THE END]" | |
ending_image = result.get("image") | |
return ( | |
gr.update(value=ending_text), | |
gr.update(value=ending_image), | |
gr.Radio(choices=[], label="", value=None, visible=False), | |
gr.update(value="", visible=False), | |
) | |
scene = result["scene"] | |
return ( | |
scene["description"], | |
scene.get("image", ""), | |
gr.Radio( | |
choices=[ch["text"] for ch in scene.get("choices", [])], | |
label="What do you choose? (select an option or write your own)", | |
value=None, | |
elem_classes=["choice-buttons"], | |
), | |
gr.update(value=""), | |
) | |
def update_preview(setting, name, age, background, personality, genre): | |
"""Update the configuration preview""" | |
if not any([setting, name, age, background, personality]): | |
return "Fill in the fields to see a preview..." | |
preview = f"""๐ SETTING: {setting[:100]}{"..." if len(setting) > 100 else ""} | |
๐ค CHARACTER: {name} (Age: {age}) | |
๐ Background: {background} | |
๐ญ Personality: {personality} | |
๐ญ GENRE: {genre}""" | |
return preview | |
async def start_game_with_music( | |
user_hash: str, | |
setting_desc: str, | |
char_name: str, | |
char_age: str, | |
char_background: str, | |
char_personality: str, | |
genre: str, | |
): | |
"""Start the game with custom settings and initialize music""" | |
yield ( | |
gr.update(visible=True), # loading indicator | |
gr.update(), # constructor_interface | |
gr.update(), # game_interface | |
gr.update(), # error_message | |
gr.update(), | |
gr.update(), | |
gr.update(), # game components unchanged | |
gr.update(), # custom choice unchanged | |
) | |
# First, get the game interface updates | |
result = await start_game_with_settings( | |
user_hash, | |
setting_desc, | |
char_name, | |
char_age, | |
char_background, | |
char_personality, | |
genre, | |
) | |
yield result | |
with gr.Blocks( | |
theme="soft", | |
title="Game Constructor & Visual Novel", | |
css=custom_css + loading_css_styles, | |
) as demo: | |
# Fullscreen Loading Indicator (hidden by default) | |
with gr.Column(visible=False, elem_id="loading-indicator") as loading_indicator: | |
gr.HTML("<div class='loading-text'>๐ Starting your adventure...</div>") | |
ls_user_hash = gr.BrowserState("", "user_hash") | |
# Constructor Interface (visible by default) | |
with gr.Column( | |
visible=True, elem_id="constructor-interface", elem_classes=["constructor-page"] | |
) as constructor_interface: | |
with gr.Row(): | |
app_description() | |
with gr.Row(): | |
error_message = gr.Textbox( | |
label="โ ๏ธ Error", | |
visible=False, | |
interactive=False, | |
elem_classes=["error-message"], | |
) | |
with gr.Row(): | |
with gr.Column(scale=2): | |
# Setting Description Section | |
with gr.Group(): | |
gr.Markdown("## ๐ Setting Description") | |
setting_suggestions = gr.Dropdown( | |
choices=["Select a suggestion..."] + SETTING_SUGGESTIONS, | |
label="Quick Suggestions", | |
value="Select a suggestion...", | |
interactive=True, | |
) | |
setting_description = gr.Textbox( | |
label="Describe your game setting", | |
placeholder="Enter a detailed description of where your story takes place...", | |
lines=4, | |
max_lines=6, | |
) | |
# Character Description Section | |
with gr.Group(): | |
gr.Markdown("## ๐ค Character Description") | |
character_suggestions = gr.Dropdown( | |
choices=["None"] | |
+ [ | |
f"{char['name']} - {char['background'][:50]}..." | |
for char in CHARACTER_SUGGESTIONS | |
], | |
label="Character Templates", | |
value="None", | |
interactive=True, | |
) | |
with gr.Row(): | |
char_name = gr.Textbox( | |
label="Character Name", | |
placeholder="Enter character name...", | |
) | |
char_age = gr.Textbox(label="Age", placeholder="25") | |
char_background = gr.Textbox( | |
label="Background/Profession", | |
placeholder="Describe your character's background, profession, or role...", | |
lines=2, | |
) | |
char_personality = gr.Textbox( | |
label="Personality & Traits", | |
placeholder="Describe personality, quirks, motivations, fears...", | |
lines=2, | |
) | |
# Genre Selection Section | |
with gr.Group(): | |
gr.Markdown("## ๐ญ Genre & Style") | |
genre_selection = gr.Dropdown( | |
choices=GENRE_OPTIONS, | |
label="Choose your story genre", | |
value=GENRE_OPTIONS[0], | |
interactive=True, | |
) | |
with gr.Column(scale=1): | |
# Preview Section | |
with gr.Group(): | |
gr.Markdown("## ๐ Configuration Preview") | |
preview_box = gr.Textbox( | |
label="Game Summary", | |
lines=8, | |
interactive=False, | |
placeholder="Fill in the fields to see a preview...", | |
) | |
with gr.Group(): | |
gr.Markdown("## ๐ฎ Ready to Play?") | |
start_btn = gr.Button("โถ๏ธ Start Game", variant="primary", size="lg") | |
with gr.Column(visible=False, elem_id="game-interface") as game_interface: | |
back_btn = gr.Button( | |
"โฌ ๏ธ Back to Constructor", | |
variant="secondary", | |
elem_id="back-btn", | |
) | |
# Audio component for background music | |
audio_out = gr.Audio( | |
autoplay=True, streaming=True, interactive=False, visible=False | |
) | |
# Background image (fullscreen) | |
with gr.Column(elem_classes=["image-container"]): | |
game_image = gr.Image(type="filepath", interactive=False, show_label=False) | |
# Overlay content (text and buttons) | |
with gr.Column(elem_classes=["overlay-content"]): | |
game_text = gr.Textbox( | |
label="", | |
interactive=False, | |
show_label=False, | |
elem_classes=["narrative-text"], | |
lines=3, | |
) | |
with gr.Column(elem_classes=["choice-area"]): | |
game_choices = gr.Radio( | |
choices=[], | |
label="What do you choose? (select an option or write your own)", | |
value=None, | |
elem_classes=["choice-buttons"], | |
) | |
custom_choice = gr.Textbox( | |
label="", | |
show_label=False, | |
placeholder="Type your option and press Enter", | |
lines=1, | |
elem_classes=["choice-input"], | |
) | |
# Event handlers for constructor interface | |
setting_suggestions.change( | |
fn=load_setting_suggestion, | |
inputs=[setting_suggestions], | |
outputs=[setting_description], | |
) | |
character_suggestions.change( | |
fn=load_character_suggestion, | |
inputs=[character_suggestions], | |
outputs=[char_name, char_age, char_background, char_personality], | |
) | |
# Update preview when any field changes | |
for component in [ | |
setting_description, | |
char_name, | |
char_age, | |
char_background, | |
char_personality, | |
genre_selection, | |
]: | |
component.change( | |
fn=update_preview, | |
inputs=[ | |
setting_description, | |
char_name, | |
char_age, | |
char_background, | |
char_personality, | |
genre_selection, | |
], | |
outputs=[preview_box], | |
) | |
# Interface switching handlers | |
start_btn.click( | |
fn=start_game_with_music, | |
inputs=[ | |
ls_user_hash, | |
setting_description, | |
char_name, | |
char_age, | |
char_background, | |
char_personality, | |
genre_selection, | |
], | |
outputs=[ | |
loading_indicator, | |
constructor_interface, | |
game_interface, | |
error_message, | |
game_text, | |
game_image, | |
game_choices, | |
custom_choice, | |
], | |
) | |
back_btn.click( | |
fn=return_to_constructor, | |
inputs=[ls_user_hash], | |
outputs=[ | |
loading_indicator, | |
constructor_interface, | |
game_interface, | |
error_message, | |
], | |
) | |
game_choices.change( | |
fn=update_scene, | |
inputs=[ls_user_hash, game_choices], | |
outputs=[game_text, game_image, game_choices, custom_choice], | |
concurrency_limit=CONCURRENCY_LIMIT, | |
) | |
custom_choice.submit( | |
fn=update_scene, | |
inputs=[ls_user_hash, custom_choice], | |
outputs=[game_text, game_image, game_choices, custom_choice], | |
) | |
demo.unload(cleanup_music_session) | |
demo.load( | |
fn=generate_user_hash, | |
inputs=[], | |
outputs=[ls_user_hash], | |
) | |
ls_user_hash.change( | |
fn=update_audio, | |
inputs=[ls_user_hash], | |
outputs=[audio_out], | |
) | |
demo.queue(default_concurrency_limit=CONCURRENCY_LIMIT) | |
demo.launch(ssr_mode=False) | |