# storyverse_weaver/app.py import gradio as gr import os import time import json from PIL import Image, ImageDraw, ImageFont import random import traceback # --- Core Logic Imports --- from core.llm_services import initialize_text_llms, is_gemini_text_ready, is_hf_text_ready, generate_text_gemini, generate_text_hf from core.image_services import initialize_image_llms, STABILITY_API_CONFIGURED, OPENAI_DALLE_CONFIGURED, generate_image_stabilityai, generate_image_dalle, ImageGenResponse from core.story_engine import Story, Scene from prompts.narrative_prompts import get_narrative_system_prompt, format_narrative_user_prompt from prompts.image_style_prompts import STYLE_PRESETS, COMMON_NEGATIVE_PROMPTS, format_image_generation_prompt from core.utils import basic_text_cleanup # --- Initialize Services --- initialize_text_llms() initialize_image_llms() # --- Get API Readiness Status --- GEMINI_TEXT_IS_READY = is_gemini_text_ready() HF_TEXT_IS_READY = is_hf_text_ready() STABILITY_API_IS_READY = STABILITY_API_CONFIGURED OPENAI_DALLE_IS_READY = OPENAI_DALLE_CONFIGURED # --- Application Configuration (Models, Defaults) --- TEXT_MODELS = {} UI_DEFAULT_TEXT_MODEL_KEY = None if GEMINI_TEXT_IS_READY: TEXT_MODELS["✨ Gemini 1.5 Flash (Narrate)"] = {"id": "gemini-1.5-flash-latest", "type": "gemini"} TEXT_MODELS["Legacy Gemini 1.0 Pro (Narrate)"] = {"id": "gemini-1.0-pro-latest", "type": "gemini"} if HF_TEXT_IS_READY: # This condition was correct TEXT_MODELS["Mistral 7B (Narrate)"] = {"id": "mistralai/Mistral-7B-Instruct-v0.2", "type": "hf_text"} TEXT_MODELS["Gemma 2B (Narrate)"] = {"id": "google/gemma-2b-it", "type": "hf_text"} if TEXT_MODELS: # Prioritize based on readiness and preference if GEMINI_TEXT_IS_READY and "✨ Gemini 1.5 Flash (Narrate)" in TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = "✨ Gemini 1.5 Flash (Narrate)" elif HF_TEXT_IS_READY and "Mistral 7B (Narrate)" in TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = "Mistral 7B (Narrate)" elif TEXT_MODELS: # Fallback to first available UI_DEFAULT_TEXT_MODEL_KEY = list(TEXT_MODELS.keys())[0] else: TEXT_MODELS["No Text Models Configured"] = {"id": "dummy_text_error", "type": "none"} UI_DEFAULT_TEXT_MODEL_KEY = "No Text Models Configured" IMAGE_PROVIDERS = {} UI_DEFAULT_IMAGE_PROVIDER_KEY = None if STABILITY_API_IS_READY: IMAGE_PROVIDERS["🎨 Stability AI (SDXL)"] = "stability_ai" if OPENAI_DALLE_IS_READY: IMAGE_PROVIDERS["πŸ–ΌοΈ DALL-E 3 (Sim.)"] = "dalle" if IMAGE_PROVIDERS: if "🎨 Stability AI (SDXL)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "🎨 Stability AI (SDXL)" elif "πŸ–ΌοΈ DALL-E 3 (Sim.)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "πŸ–ΌοΈ DALL-E 3 (Sim.)" else: UI_DEFAULT_IMAGE_PROVIDER_KEY = list(IMAGE_PROVIDERS.keys())[0] else: IMAGE_PROVIDERS["No Image Providers Configured"] = "none" UI_DEFAULT_IMAGE_PROVIDER_KEY = "No Image Providers Configured" # --- Gradio UI Theme and CSS --- omega_theme = gr.themes.Base( font=[gr.themes.GoogleFont("Lexend Deca"), "ui-sans-serif", "system-ui", "sans-serif"], primary_hue=gr.themes.colors.purple, secondary_hue=gr.themes.colors.pink, neutral_hue=gr.themes.colors.slate ).set( body_background_fill="#0F0F1A", block_background_fill="#1A1A2E", block_border_width="1px", block_border_color="#2A2A4A", block_label_background_fill="#2A2A4A", input_background_fill="#2A2A4A", input_border_color="#4A4A6A", button_primary_background_fill="linear-gradient(135deg, #7F00FF 0%, #E100FF 100%)", button_primary_text_color="white", button_secondary_background_fill="#4A4A6A", button_secondary_text_color="#E0E0FF", slider_color="#A020F0" ) omega_css = """ body, .gradio-container { background-color: #0F0F1A !important; color: #D0D0E0 !important; } .gradio-container { max-width: 1400px !important; margin: auto !important; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); padding: 25px !important; border: 1px solid #2A2A4A;} .gr-panel, .gr-box, .gr-accordion { background-color: #1A1A2E !important; border: 1px solid #2A2A4A !important; border-radius: 12px !important; box-shadow: 0 4px 15px rgba(0,0,0,0.1);} .gr-markdown h1 { font-size: 2.8em !important; text-align: center; color: transparent; background: linear-gradient(135deg, #A020F0 0%, #E040FB 100%); -webkit-background-clip: text; background-clip: text; margin-bottom: 5px !important; letter-spacing: -1px;} .gr-markdown h3 { color: #C080F0 !important; text-align: center; font-weight: 400; margin-bottom: 25px !important;} .input-section-header { font-size: 1.6em; font-weight: 600; color: #D0D0FF; margin-top: 15px; margin-bottom: 8px; border-bottom: 2px solid #7F00FF; padding-bottom: 5px;} .output-section-header { font-size: 1.8em; font-weight: 600; color: #D0D0FF; margin-top: 15px; margin-bottom: 12px;} .gr-input input, .gr-input textarea, .gr-dropdown select, .gr-textbox textarea { background-color: #2A2A4A !important; color: #E0E0FF !important; border: 1px solid #4A4A6A !important; border-radius: 8px !important; padding: 10px !important;} .gr-button { border-radius: 8px !important; font-weight: 500 !important; transition: all 0.2s ease-in-out !important;} .gr-button-primary:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(127,0,255,0.3) !important; } .panel_image { border-radius: 12px !important; overflow: hidden; box-shadow: 0 6px 15px rgba(0,0,0,0.25) !important; background-color: #23233A;} .panel_image img { max-height: 600px !important; } .gallery_output { background-color: transparent !important; border: none !important; } .gallery_output .thumbnail-item { border-radius: 8px !important; box-shadow: 0 3px 8px rgba(0,0,0,0.2) !important; margin: 6px !important; transition: transform 0.2s ease; height: 180px !important; width: 180px !important;} .gallery_output .thumbnail-item:hover { transform: scale(1.05); } .status_text { font-weight: 500; padding: 12px 18px; text-align: center; border-radius: 8px; margin-top:12px; border: 1px solid transparent; font-size: 1.05em;} .error_text { background-color: #401010 !important; color: #FFB0B0 !important; border-color: #802020 !important; } .success_text { background-color: #104010 !important; color: #B0FFB0 !important; border-color: #208020 !important;} .processing_text { background-color: #102040 !important; color: #B0D0FF !important; border-color: #204080 !important;} .important-note { background-color: rgba(127,0,255,0.1); border-left: 5px solid #7F00FF; padding: 15px; margin-bottom:20px; color: #E0E0FF; border-radius: 6px;} .gr-tabitem { background-color: #1A1A2E !important; border-radius: 0 0 12px 12px !important; padding: 15px !important;} .gr-tab-button.selected { background-color: #2A2A4A !important; color: white !important; border-bottom: 3px solid #A020F0 !important; border-radius: 8px 8px 0 0 !important; font-weight: 600 !important;} .gr-tab-button { color: #A0A0C0 !important; border-radius: 8px 8px 0 0 !important;} .gr-accordion > .gr-block { border-top: 1px solid #2A2A4A !important; } .gr-markdown code { background-color: #2A2A4A !important; color: #C0C0E0 !important; padding: 0.2em 0.5em; border-radius: 4px; } .gr-markdown pre { background-color: #23233A !important; padding: 1em !important; border-radius: 6px !important; border: 1px solid #2A2A4A !important;} .gr-markdown pre > code { padding: 0 !important; background-color: transparent !important; } #surprise_button { background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%) !important; font-weight:600 !important;} #surprise_button:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(255,126,95,0.3) !important; } """ # --- Helper: Placeholder Image Creation (Defined at global scope) --- def create_placeholder_image(text="Processing...", size=(512, 512), color="#23233A", text_color="#E0E0FF"): img = Image.new('RGB', size, color=color); draw = ImageDraw.Draw(img) try: font_path = "arial.ttf" if os.path.exists("arial.ttf") else None except: font_path = None try: font = ImageFont.truetype(font_path, 40) if font_path else ImageFont.load_default() except IOError: font = ImageFont.load_default() if hasattr(draw, 'textbbox'): bbox = draw.textbbox((0,0), text, font=font); tw, th = bbox[2]-bbox[0], bbox[3]-bbox[1] else: tw, th = draw.textsize(text, font=font) draw.text(((size[0]-tw)/2, (size[1]-th)/2), text, font=font, fill=text_color); return img # --- StoryVerse Weaver Orchestrator --- def add_scene_to_story_orchestrator( current_story_obj: Story, scene_prompt_text: str, image_style_dropdown: str, artist_style_text: str, negative_prompt_text: str, text_model_key: str, image_provider_key: str, narrative_length: str, image_quality: str, progress=gr.Progress(track_tqdm=True) ): start_time = time.time() if not current_story_obj: current_story_obj = Story() log_accumulator = [f"**πŸš€ Scene {current_story_obj.current_scene_number + 1} - {time.strftime('%H:%M:%S')}**"] # Initialize placeholders for the final return tuple values ret_story_state = current_story_obj ret_gallery = current_story_obj.get_all_scenes_for_gallery_display() ret_latest_image = None ret_latest_narrative_str = "## Processing...\nNarrative being woven..." ret_status_bar_html = "

Processing...

" # ret_log_md will be built up # Initial UI update using direct component updates in yield # These component variables (output_status_bar etc.) must be defined in the `with gr.Blocks` scope yield { output_status_bar: gr.HTML(value=f"

🌌 Weaving Scene {current_story_obj.current_scene_number + 1}...

"), output_latest_scene_image: gr.Image(value=create_placeholder_image("🎨 Conjuring visuals...")), # Removed visible=True, Gradio handles it output_latest_scene_narrative: gr.Markdown(value=" Musing narrative..."), # Removed visible=True # engage_button and surprise_button will be handled by the .then() chaining in the UI definition output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) } try: if not scene_prompt_text.strip(): raise ValueError("Scene prompt cannot be empty!") # --- 1. Generate Narrative Text --- progress(0.1, desc="✍️ Crafting narrative...") narrative_text_generated = f"Narrative Error: Init failed." text_model_info = TEXT_MODELS.get(text_model_key) if text_model_info and text_model_info["type"] != "none": system_p = get_narrative_system_prompt("default") prev_narrative = current_story_obj.get_last_scene_narrative() user_p = format_narrative_user_prompt(scene_prompt_text, prev_narrative) log_accumulator.append(f" Narrative: Using {text_model_key} ({text_model_info['id']}). Length: {narrative_length}") text_response = None if text_model_info["type"] == "gemini": text_response = generate_text_gemini(user_p, model_id=text_model_info["id"], system_prompt=system_p, max_tokens=768 if narrative_length.startswith("Detailed") else 400) elif text_model_info["type"] == "hf_text": text_response = generate_text_hf(user_p, model_id=text_model_info["id"], system_prompt=system_p, max_tokens=768 if narrative_length.startswith("Detailed") else 400) if text_response and text_response.success: narrative_text_generated = basic_text_cleanup(text_response.text) log_accumulator.append(f" Narrative: Success. (Snippet: {narrative_text_generated[:50]}...)") elif text_response: narrative_text_generated = f"**Narrative Error ({text_model_key}):** {text_response.error}" log_accumulator.append(f" Narrative: FAILED - {text_response.error}") else: log_accumulator.append(f" Narrative: FAILED - No response object from {text_model_key}.") else: narrative_text_generated = "**Narrative Error:** Selected text model not available or misconfigured." log_accumulator.append(f" Narrative: FAILED - Model '{text_model_key}' not available.") ret_latest_narrative_str = f"## Scene Idea: {scene_prompt_text}\n\n{narrative_text_generated}" yield { output_latest_scene_narrative: gr.Markdown(value=ret_latest_narrative_str), output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) } # --- 2. Generate Image --- progress(0.5, desc="🎨 Conjuring visuals...") image_generated_pil = None image_generation_error_message = None selected_image_provider_type = IMAGE_PROVIDERS.get(image_provider_key) image_content_prompt_for_gen = narrative_text_generated if narrative_text_generated and "Error" not in narrative_text_generated else scene_prompt_text quality_keyword = "ultra detailed, intricate, masterpiece, " if image_quality == "High Detail" else ("concept sketch, line art, " if image_quality == "Sketch Concept" else "") full_image_prompt = format_image_generation_prompt(quality_keyword + image_content_prompt_for_gen[:350], image_style_dropdown, artist_style_text) log_accumulator.append(f" Image: Using {image_provider_key}. Style: {image_style_dropdown}. Artist: {artist_style_text or 'N/A'}.") if selected_image_provider_type and selected_image_provider_type != "none": image_response = None if selected_image_provider_type == "stability_ai": image_response = generate_image_stabilityai(full_image_prompt, negative_prompt=negative_prompt_text or COMMON_NEGATIVE_PROMPTS, steps=40 if image_quality=="High Detail" else 25) elif selected_image_provider_type == "dalle": image_response = generate_image_dalle(full_image_prompt, quality="hd" if image_quality=="High Detail" else "standard") if image_response and image_response.success: image_generated_pil = image_response.image log_accumulator.append(f" Image: Success from {image_response.provider}.") elif image_response: image_generation_error_message = f"**Image Error ({image_response.provider}):** {image_response.error}" log_accumulator.append(f" Image: FAILED - {image_response.error}") else: image_generation_error_message = f"**Image Error:** No response object from {image_provider_key} service." log_accumulator.append(f" Image: FAILED - No response object from {image_provider_key}.") else: image_generation_error_message = "**Image Error:** Selected image provider not available or misconfigured." log_accumulator.append(f" Image: FAILED - Provider '{image_provider_key}' unavailable.") ret_latest_image = image_generated_pil if image_generated_pil else create_placeholder_image("Image Gen Failed", color="#401010") yield { output_latest_scene_image: gr.Image(value=ret_latest_image), output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) } # --- 3. Add Scene to Story Object --- final_scene_error = None if image_generation_error_message and "**Narrative Error**" in narrative_text_generated : final_scene_error = f"{narrative_text_generated}\n{image_generation_error_message}" elif "**Narrative Error**" in narrative_text_generated: final_scene_error = narrative_text_generated elif image_generation_error_message: final_scene_error = image_generation_error_message current_story_obj.add_scene_from_elements( user_prompt=scene_prompt_text, narrative_text=narrative_text_generated if "**Narrative Error**" not in narrative_text_generated else "(Narrative gen failed)", image=image_generated_pil, image_style_prompt=f"{image_style_dropdown}{f', by {artist_style_text}' if artist_style_text and artist_style_text.strip() else ''}", image_provider=image_provider_key if selected_image_provider_type != "none" else "N/A", error_message=final_scene_error ) ret_story_state = current_story_obj log_accumulator.append(f" Scene {current_story_obj.current_scene_number} processed and added.") # --- 4. Prepare Final Values for Return Tuple --- ret_gallery = current_story_obj.get_all_scenes_for_gallery_display() _ , latest_narr_for_display_final_str_temp = current_story_obj.get_latest_scene_details_for_display() ret_latest_narrative_str = latest_narr_for_display_final_str_temp status_html_str_temp = f"

Scene {current_story_obj.current_scene_number} added with errors.

" if final_scene_error else f"

🌌 Scene {current_story_obj.current_scene_number} woven!

" ret_status_bar_html = gr.HTML(value=status_html_str_temp) progress(1.0, desc="Scene Complete!") except ValueError as ve: log_accumulator.append(f"\n**INPUT/CONFIG ERROR:** {ve}") ret_status_bar_html = gr.HTML(value=f"

❌ CONFIGURATION ERROR: {ve}

") ret_latest_narrative_str = f"## Error\n{ve}" except Exception as e: log_accumulator.append(f"\n**UNEXPECTED RUNTIME ERROR:** {type(e).__name__} - {e}\n{traceback.format_exc()}") ret_status_bar_html = gr.HTML(value=f"

❌ UNEXPECTED ERROR: {type(e).__name__}. Check logs.

") ret_latest_narrative_str = f"## Unexpected Error\n{type(e).__name__}: {e}\nSee log for details." # No `finally` block here for button updates; handled by `.then()` current_total_time = time.time() - start_time log_accumulator.append(f" Cycle ended at {time.strftime('%H:%M:%S')}. Total time: {current_total_time:.2f}s") ret_log_str = "\n".join(log_accumulator) # This is the FINAL return. It must be a tuple matching the `outputs` list of engage_button.click() return ( ret_story_state, ret_gallery, ret_latest_image, gr.Markdown(value=ret_latest_narrative_str), ret_status_bar_html, gr.Markdown(value=ret_log_str) ) def clear_story_state_ui_wrapper(): new_story = Story() placeholder_img = create_placeholder_image("Your StoryVerse is a blank canvas...", color="#1A1A2E", text_color="#A0A0C0") cleared_gallery = [(placeholder_img, "Your StoryVerse is new and untold...")] initial_narrative = "## ✨ A New Story Begins ✨\nDescribe your first scene idea in the panel to the left and let the AI help you weave your world!" status_msg = "

πŸ“œ Story Cleared. A fresh canvas awaits your imagination!

" return (new_story, cleared_gallery, None, gr.Markdown(value=initial_narrative), gr.HTML(value=status_msg), "Log Cleared. Ready for a new adventure!", "") def surprise_me_func(): themes = ["Cosmic Horror", "Solarpunk Utopia", "Mythic Fantasy", "Noir Detective", "Silent Film Comedy", "Deep Sea Exploration", "Prehistoric Survival"] actions = ["unearths an artifact of immense power", "negotiates with an interdimensional being", "solves an ancient riddle", "embarks on a perilous journey", "attends a secret festival", "witnesses a celestial event", "finds a hidden sanctuary"] settings = ["on a rogue planet drifting through an empty void", "in a city built within a colossal, living tree", "within a library containing all possible books", "on a generation ship nearing its ancient destination", "in a dreamlike landscape where physics is suggestive", "at the bottom of a Mariana Trench-like abyss", "in a lush jungle teeming with dinosaurs"] prompt = f"A protagonist {random.choice(actions)} {random.choice(settings)}. The overall theme is {random.choice(themes)}." style = random.choice(list(STYLE_PRESETS.keys())) artist = random.choice(["H.R. Giger", "Moebius", "Eyvind Earle", " Remedios Varo", "Alphonse Mucha", ""]*2) return prompt, style, artist # --- Functions to control button interactivity --- def disable_buttons_for_processing(): return gr.Button(interactive=False), gr.Button(interactive=False) def enable_buttons_after_processing(): return gr.Button(interactive=True), gr.Button(interactive=True) # --- Gradio UI Definition --- with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨ - AI Story & World Weaver") as story_weaver_demo: story_state_output = gr.State(Story()) gr.Markdown("

✨ StoryVerse Omega ✨

\n

Craft Immersive Multimodal Worlds with AI

") gr.HTML("
Welcome, Worldsmith! Describe your vision, choose your style, and let Omega help you weave captivating scenes with narrative and imagery. Ensure API keys (STORYVERSE_...) are correctly set in Space Secrets!
") with gr.Accordion("πŸ”§ AI Services Status & Info", open=False): status_text_list = [] text_llm_ok, image_gen_ok = (GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY), (STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY) if not text_llm_ok and not image_gen_ok: status_text_list.append("

⚠️ CRITICAL: NO AI SERVICES CONFIGURED.

") else: if text_llm_ok: status_text_list.append("

βœ… Text Generation Service(s) Ready.

") else: status_text_list.append("

⚠️ Text Generation Service(s) NOT Ready.

") if image_gen_ok: status_text_list.append("

βœ… Image Generation Service(s) Ready.

") else: status_text_list.append("

⚠️ Image Generation Service(s) NOT Ready.

") gr.HTML("".join(status_text_list)) with gr.Row(equal_height=False, variant="panel"): with gr.Column(scale=7, min_width=450): gr.Markdown("### πŸ’‘ **Craft Your Scene**", elem_classes="input-section-header") with gr.Group(): scene_prompt_input = gr.Textbox(lines=7, label="Scene Vision (Description, Dialogue, Action):", placeholder="e.g., Amidst swirling cosmic dust...") with gr.Row(elem_classes=["compact-row"]): with gr.Column(scale=2): image_style_input = gr.Dropdown(choices=["Default (Cinematic Realism)"] + sorted(list(STYLE_PRESETS.keys())), value="Default (Cinematic Realism)", label="Visual Style Preset") with gr.Column(scale=2): artist_style_input = gr.Textbox(label="Artistic Inspiration (Optional):", placeholder="e.g., Moebius...") negative_prompt_input = gr.Textbox(lines=2, label="Exclude from Image (Negative Prompt):", value=COMMON_NEGATIVE_PROMPTS) with gr.Accordion("βš™οΈ Advanced AI Configuration", open=False): with gr.Group(): text_model_dropdown = gr.Dropdown(choices=list(TEXT_MODELS.keys()), value=UI_DEFAULT_TEXT_MODEL_KEY, label="Narrative AI Engine") image_provider_dropdown = gr.Dropdown(choices=list(IMAGE_PROVIDERS.keys()), value=UI_DEFAULT_IMAGE_PROVIDER_KEY, label="Visual AI Engine") with gr.Row(): narrative_length_dropdown = gr.Dropdown(["Short (1 paragraph)", "Medium (2-3 paragraphs)", "Detailed (4+ paragraphs)"], value="Medium (2-3 paragraphs)", label="Narrative Detail") image_quality_dropdown = gr.Dropdown(["Standard", "High Detail", "Sketch Concept"], value="Standard", label="Image Detail/Style") with gr.Row(elem_classes=["compact-row"], equal_height=True): engage_button = gr.Button("🌌 Weave This Scene!", variant="primary", scale=3, icon="✨") surprise_button = gr.Button("🎲 Surprise Me!", variant="secondary", scale=1, icon="🎁") clear_story_button = gr.Button("πŸ—‘οΈ New Story", variant="stop", scale=1, icon="♻️") output_status_bar = gr.HTML(value="

Ready to weave your first masterpiece!

") with gr.Column(scale=10, min_width=700): gr.Markdown("### πŸ–ΌοΈ **Your Evolving StoryVerse**", elem_classes="output-section-header") with gr.Tabs(): with gr.TabItem("🌠 Latest Scene", id="latest_scene_tab"): output_latest_scene_image = gr.Image(label="Latest Scene Image", type="pil", interactive=False, show_download_button=True, height=512, show_label=False, elem_classes=["panel_image"]) output_latest_scene_narrative = gr.Markdown() with gr.TabItem("πŸ“š Story Scroll", id="story_scroll_tab"): output_gallery = gr.Gallery(label="Story Scroll", show_label=False, columns=4, object_fit="cover", height=700, preview=True, allow_preview=True, elem_classes=["gallery_output"]) with gr.TabItem("βš™οΈ Interaction Log", id="log_tab"): with gr.Accordion(label="Developer Interaction Log", open=False): output_interaction_log_markdown = gr.Markdown("Log will appear here...") # Chained event handling for engage_button click_event_engage = engage_button.click( fn=disable_buttons_for_processing, # Step 1: Disable buttons inputs=None, outputs=[engage_button, surprise_button], queue=False # Run immediately without queuing for this UI update ).then( fn=add_scene_to_story_orchestrator, # Step 2: Run the main orchestrator inputs=[ story_state_output, scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input, text_model_dropdown, image_provider_dropdown, narrative_length_dropdown, image_quality_dropdown ], outputs=[ # These are updated by the FINAL RETURN of the orchestrator story_state_output, output_gallery, output_latest_scene_image, output_latest_scene_narrative, output_status_bar, output_interaction_log_markdown ] # Progressive updates within orchestrator happen via `yield {component_var: update}` ).then( fn=enable_buttons_after_processing, # Step 3: Re-enable buttons inputs=None, outputs=[engage_button, surprise_button], queue=False # Run immediately after orchestrator finishes ) clear_story_button.click( fn=clear_story_state_ui_wrapper, inputs=[], outputs=[ story_state_output, output_gallery, output_latest_scene_image, output_latest_scene_narrative, output_status_bar, output_interaction_log_markdown, scene_prompt_input ] ) surprise_button.click( fn=surprise_me_func, inputs=[], outputs=[scene_prompt_input, image_style_input, artist_style_input] ) gr.Examples( examples=[ ["A lone, weary traveler on a mechanical steed crosses a vast, crimson desert under twin suns. Dust devils dance in the distance.", "Sci-Fi Western", "Moebius", "greenery, water, modern city"], ["Deep within an ancient, bioluminescent forest, a hidden civilization of sentient fungi perform a mystical ritual around a pulsating crystal.", "Psychedelic Fantasy", "Alex Grey", "technology, buildings, roads"], ["A child sits on a crescent moon, fishing for stars in a swirling nebula. A friendly space whale swims nearby.", "Whimsical Cosmic", "James Jean", "realistic, dark, scary"], ["A grand, baroque library where the books fly freely and whisper forgotten lore to those who listen closely.", "Magical Realism", "Remedios Varo", "minimalist, simple, technology"] ], inputs=[scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input], label="🌌 Example Universes to Weave 🌌", ) gr.HTML("

✨ StoryVerse Omegaβ„’ - Weaving Worlds with Words and Pixels ✨

") # --- Entry Point --- if __name__ == "__main__": print("="*80) print("✨ StoryVerse Omegaβ„’ - AI Story & World Weaver - Launching... ✨") print(f" Text LLM Ready (Gemini): {GEMINI_TEXT_IS_READY}") print(f" Text LLM Ready (HF): {HF_TEXT_IS_READY}") print(f" Image Provider Ready (Stability AI): {STABILITY_API_IS_READY}") print(f" Image Provider Ready (DALL-E): {OPENAI_DALLE_IS_READY}") if not (GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY) or not (STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY): print(" πŸ”΄ WARNING: Not all required AI services are configured correctly.") print(f" Default Text Model: {UI_DEFAULT_TEXT_MODEL_KEY}") print(f" Default Image Provider: {UI_DEFAULT_IMAGE_PROVIDER_KEY}") print("="*80) story_weaver_demo.launch(debug=True, server_name="0.0.0.0", share=False)