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 | |
| gr.update(), | |
| ) | |
| # 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") | |
| ls_game_started = gr.BrowserState(False, "game_started") | |
| # 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, | |
| ls_game_started, | |
| ], | |
| ) | |
| 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.load( | |
| fn=generate_user_hash, | |
| inputs=[], | |
| outputs=[ls_user_hash], | |
| ) | |
| ls_game_started.change( | |
| fn=update_audio, | |
| inputs=[ls_user_hash, ls_game_started], | |
| outputs=[audio_out], | |
| ) | |
| demo.queue(default_concurrency_limit=CONCURRENCY_LIMIT) | |
| demo.launch(ssr_mode=False) | |