Spaces:
Running
Running
| import gradio as gr | |
| from css import custom_css, loading_css_styles | |
| from audio.audio_generator import ( | |
| update_audio, | |
| change_music_tone, | |
| cleanup_music_session, | |
| ) | |
| import logging | |
| from agent.llm_agent import process_user_input | |
| from images.image_generator import modify_image | |
| import uuid | |
| from game_state import story, state | |
| from game_constructor import ( | |
| SETTING_SUGGESTIONS, | |
| CHARACTER_SUGGESTIONS, | |
| GENRE_OPTIONS, | |
| load_setting_suggestion, | |
| load_character_suggestion, | |
| start_game_with_settings, | |
| ) | |
| import asyncio | |
| from game_setting import get_user_story | |
| from config import settings | |
| logger = logging.getLogger(__name__) | |
| def return_to_constructor(): | |
| """Return to the game constructor interface, ensure loading is hidden.""" | |
| 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 | |
| ) | |
| async def update_scene(user_hash: str, choice): | |
| logger.info(f"Updating scene with choice: {choice}") | |
| if isinstance(choice, str): | |
| old_scene = state["scene"] | |
| new_scene = str(uuid.uuid4()) | |
| story[new_scene] = { | |
| **story[old_scene], | |
| } | |
| state["scene"] = new_scene | |
| user_story = get_user_story( | |
| story[old_scene]["text"], story[old_scene]["img_description"], choice | |
| ) | |
| response = await ( | |
| story[old_scene]["choices"][choice] or process_user_input(user_story) | |
| ) | |
| story[new_scene]["text"] = response.game_message | |
| story[new_scene]["choices"] = { | |
| option.option_description: asyncio.create_task( | |
| process_user_input( | |
| get_user_story( | |
| response.game_message, | |
| response.change_scene.scene_description, | |
| option.option_description, | |
| ) | |
| ) | |
| ) | |
| if settings.pregenerate_next_scene | |
| else None | |
| for option in response.player_options | |
| } | |
| img_task = None | |
| # always modify the image to avoid hallucinations in which image is being generated in entirely different style | |
| if ( | |
| response.change_scene.change_scene == "change_completely" | |
| or response.change_scene.change_scene == "modify" | |
| ): | |
| img_task = modify_image( | |
| story[old_scene]["image"], response.change_scene.scene_description | |
| ) | |
| else: | |
| img_task = asyncio.sleep(0) | |
| # run both tasks in parallel | |
| img_res, _ = await asyncio.gather( | |
| img_task, change_music_tone(user_hash, response.music_prompt) | |
| ) | |
| if img_res and response.change_scene.change_scene: | |
| img_path, img_description = img_res | |
| if img_path: | |
| story[new_scene]["image"] = img_path | |
| story[new_scene]["img_description"] = img_description | |
| scene = story[state["scene"]] | |
| return ( | |
| scene["text"], | |
| scene["image"], | |
| gr.Radio( | |
| choices=scene["choices"], | |
| label="What do you choose?", | |
| value=None, | |
| elem_classes=["choice-buttons"], | |
| ), | |
| ) | |
| 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 | |
| ) | |
| # 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>") | |
| local_storage = gr.BrowserState(str(uuid.uuid4()), "user_hash") | |
| # Constructor Interface (visible by default) | |
| with gr.Column( | |
| visible=True, elem_id="constructor-interface" | |
| ) as constructor_interface: | |
| gr.Markdown("# ๐ฎ Interactive Game Constructor") | |
| gr.Markdown( | |
| "Create your own interactive story game by defining the setting, character, and genre!" | |
| ) | |
| # Error message area | |
| 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: | |
| gr.Markdown("# ๐ฎ Your Interactive Story") | |
| with gr.Row(): | |
| back_btn = gr.Button("โฌ ๏ธ Back to Constructor", variant="secondary") | |
| gr.Markdown("### Playing your custom game!") | |
| # 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, | |
| ) | |
| game_choices = gr.Radio( | |
| choices=[], | |
| label="What do you choose?", | |
| value=None, | |
| elem_classes=["choice-buttons"], | |
| ) | |
| # 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=[ | |
| local_storage, | |
| 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, | |
| ], | |
| ) | |
| back_btn.click( | |
| fn=return_to_constructor, | |
| inputs=[], | |
| outputs=[ | |
| loading_indicator, | |
| constructor_interface, | |
| game_interface, | |
| error_message, | |
| ], | |
| ) | |
| game_choices.change( | |
| fn=update_scene, | |
| inputs=[local_storage, game_choices], | |
| outputs=[game_text, game_image, game_choices], | |
| ) | |
| demo.unload(cleanup_music_session) | |
| demo.load( | |
| fn=update_audio, | |
| inputs=[local_storage], | |
| outputs=[audio_out], | |
| ) | |
| demo.launch(ssr_mode=False) | |