mgbam commited on
Commit
70efebe
Β·
verified Β·
1 Parent(s): ae9e00c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -81
app.py CHANGED
@@ -9,7 +9,7 @@ import traceback
9
 
10
  # --- Core Logic Imports ---
11
  from core.llm_services import initialize_text_llms, is_gemini_text_ready, is_hf_text_ready, generate_text_gemini, generate_text_hf
12
- from core.image_services import initialize_image_llms, STABILITY_API_CONFIGURED, OPENAI_DALLE_CONFIGURED, generate_image_stabilityai, generate_image_dalle, ImageGenResponse
13
  from core.story_engine import Story, Scene
14
  from prompts.narrative_prompts import get_narrative_system_prompt, format_narrative_user_prompt
15
  from prompts.image_style_prompts import STYLE_PRESETS, COMMON_NEGATIVE_PROMPTS, format_image_generation_prompt
@@ -21,9 +21,10 @@ initialize_image_llms()
21
 
22
  # --- Get API Readiness Status ---
23
  GEMINI_TEXT_IS_READY = is_gemini_text_ready()
24
- HF_TEXT_IS_READY = is_hf_text_ready()
25
  STABILITY_API_IS_READY = STABILITY_API_CONFIGURED
26
  OPENAI_DALLE_IS_READY = OPENAI_DALLE_CONFIGURED
 
27
 
28
  # --- Application Configuration (Models, Defaults) ---
29
  TEXT_MODELS = {}
@@ -31,35 +32,38 @@ UI_DEFAULT_TEXT_MODEL_KEY = None
31
  if GEMINI_TEXT_IS_READY:
32
  TEXT_MODELS["✨ Gemini 1.5 Flash (Narrate)"] = {"id": "gemini-1.5-flash-latest", "type": "gemini"}
33
  TEXT_MODELS["Legacy Gemini 1.0 Pro (Narrate)"] = {"id": "gemini-1.0-pro-latest", "type": "gemini"}
34
- if HF_TEXT_IS_READY: # This condition was correct
35
- TEXT_MODELS["Mistral 7B (Narrate)"] = {"id": "mistralai/Mistral-7B-Instruct-v0.2", "type": "hf_text"}
36
- TEXT_MODELS["Gemma 2B (Narrate)"] = {"id": "google/gemma-2b-it", "type": "hf_text"}
37
 
38
  if TEXT_MODELS:
39
- # Prioritize based on readiness and preference
40
- if GEMINI_TEXT_IS_READY and "✨ Gemini 1.5 Flash (Narrate)" in TEXT_MODELS:
41
- UI_DEFAULT_TEXT_MODEL_KEY = "✨ Gemini 1.5 Flash (Narrate)"
42
- elif HF_TEXT_IS_READY and "Mistral 7B (Narrate)" in TEXT_MODELS:
43
- UI_DEFAULT_TEXT_MODEL_KEY = "Mistral 7B (Narrate)"
44
- elif TEXT_MODELS: # Fallback to first available
45
- UI_DEFAULT_TEXT_MODEL_KEY = list(TEXT_MODELS.keys())[0]
46
  else:
47
  TEXT_MODELS["No Text Models Configured"] = {"id": "dummy_text_error", "type": "none"}
48
  UI_DEFAULT_TEXT_MODEL_KEY = "No Text Models Configured"
49
 
 
50
  IMAGE_PROVIDERS = {}
51
  UI_DEFAULT_IMAGE_PROVIDER_KEY = None
 
52
  if STABILITY_API_IS_READY: IMAGE_PROVIDERS["🎨 Stability AI (SDXL)"] = "stability_ai"
 
 
 
53
  if OPENAI_DALLE_IS_READY: IMAGE_PROVIDERS["πŸ–ΌοΈ DALL-E 3 (Sim.)"] = "dalle"
54
 
55
  if IMAGE_PROVIDERS:
56
- if "🎨 Stability AI (SDXL)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "🎨 Stability AI (SDXL)"
57
- elif "πŸ–ΌοΈ DALL-E 3 (Sim.)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "πŸ–ΌοΈ DALL-E 3 (Sim.)"
 
58
  else: UI_DEFAULT_IMAGE_PROVIDER_KEY = list(IMAGE_PROVIDERS.keys())[0]
59
  else:
60
  IMAGE_PROVIDERS["No Image Providers Configured"] = "none"
61
  UI_DEFAULT_IMAGE_PROVIDER_KEY = "No Image Providers Configured"
62
 
 
63
  # --- Gradio UI Theme and CSS ---
64
  omega_theme = gr.themes.Base(
65
  font=[gr.themes.GoogleFont("Lexend Deca"), "ui-sans-serif", "system-ui", "sans-serif"],
@@ -81,6 +85,7 @@ body, .gradio-container { background-color: #0F0F1A !important; color: #D0D0E0 !
81
  .output-section-header { font-size: 1.8em; font-weight: 600; color: #D0D0FF; margin-top: 15px; margin-bottom: 12px;}
82
  .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;}
83
  .gr-button { border-radius: 8px !important; font-weight: 500 !important; transition: all 0.2s ease-in-out !important;}
 
84
  .gr-button-primary:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(127,0,255,0.3) !important; }
85
  .panel_image { border-radius: 12px !important; overflow: hidden; box-shadow: 0 6px 15px rgba(0,0,0,0.25) !important; background-color: #23233A;}
86
  .panel_image img { max-height: 600px !important; }
@@ -103,8 +108,9 @@ body, .gradio-container { background-color: #0F0F1A !important; color: #D0D0E0 !
103
  #surprise_button:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(255,126,95,0.3) !important; }
104
  """
105
 
106
- # --- Helper: Placeholder Image Creation (Defined at global scope) ---
107
  def create_placeholder_image(text="Processing...", size=(512, 512), color="#23233A", text_color="#E0E0FF"):
 
108
  img = Image.new('RGB', size, color=color); draw = ImageDraw.Draw(img)
109
  try: font_path = "arial.ttf" if os.path.exists("arial.ttf") else None
110
  except: font_path = None
@@ -114,6 +120,7 @@ def create_placeholder_image(text="Processing...", size=(512, 512), color="#2323
114
  else: tw, th = draw.textsize(text, font=font)
115
  draw.text(((size[0]-tw)/2, (size[1]-th)/2), text, font=font, fill=text_color); return img
116
 
 
117
  # --- StoryVerse Weaver Orchestrator ---
118
  def add_scene_to_story_orchestrator(
119
  current_story_obj: Story, scene_prompt_text: str, image_style_dropdown: str, artist_style_text: str,
@@ -126,21 +133,20 @@ def add_scene_to_story_orchestrator(
126
 
127
  log_accumulator = [f"**πŸš€ Scene {current_story_obj.current_scene_number + 1} - {time.strftime('%H:%M:%S')}**"]
128
 
129
- # Initialize placeholders for the final return tuple values
130
  ret_story_state = current_story_obj
131
  ret_gallery = current_story_obj.get_all_scenes_for_gallery_display()
132
- ret_latest_image = None
133
- ret_latest_narrative_str = "## Processing...\nNarrative being woven..."
134
- ret_status_bar_html = "<p class='processing_text status_text'>Processing...</p>"
135
- # ret_log_md will be built up
136
 
137
- # Initial UI update using direct component updates in yield
138
- # These component variables (output_status_bar etc.) must be defined in the `with gr.Blocks` scope
139
  yield {
140
- output_status_bar: gr.HTML(value=f"<p class='processing_text status_text'>🌌 Weaving Scene {current_story_obj.current_scene_number + 1}...</p>"),
141
- output_latest_scene_image: gr.Image(value=create_placeholder_image("🎨 Conjuring visuals...")), # Removed visible=True, Gradio handles it
142
- output_latest_scene_narrative: gr.Markdown(value=" Musing narrative..."), # Removed visible=True
143
- # engage_button and surprise_button will be handled by the .then() chaining in the UI definition
144
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator))
145
  }
146
 
@@ -173,8 +179,9 @@ def add_scene_to_story_orchestrator(
173
  narrative_text_generated = "**Narrative Error:** Selected text model not available or misconfigured."
174
  log_accumulator.append(f" Narrative: FAILED - Model '{text_model_key}' not available.")
175
 
176
- ret_latest_narrative_str = f"## Scene Idea: {scene_prompt_text}\n\n{narrative_text_generated}"
177
- yield { output_latest_scene_narrative: gr.Markdown(value=ret_latest_narrative_str),
 
178
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) }
179
 
180
  # --- 2. Generate Image ---
@@ -191,6 +198,8 @@ def add_scene_to_story_orchestrator(
191
  image_response = None
192
  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)
193
  elif selected_image_provider_type == "dalle": image_response = generate_image_dalle(full_image_prompt, quality="hd" if image_quality=="High Detail" else "standard")
 
 
194
 
195
  if image_response and image_response.success:
196
  image_generated_pil = image_response.image
@@ -229,55 +238,43 @@ def add_scene_to_story_orchestrator(
229
  # --- 4. Prepare Final Values for Return Tuple ---
230
  ret_gallery = current_story_obj.get_all_scenes_for_gallery_display()
231
  _ , latest_narr_for_display_final_str_temp = current_story_obj.get_latest_scene_details_for_display()
232
- ret_latest_narrative_str = latest_narr_for_display_final_str_temp
233
 
234
  status_html_str_temp = f"<p class='error_text status_text'>Scene {current_story_obj.current_scene_number} added with errors.</p>" if final_scene_error else f"<p class='success_text status_text'>🌌 Scene {current_story_obj.current_scene_number} woven!</p>"
235
- ret_status_bar_html = gr.HTML(value=status_html_str_temp)
236
 
237
  progress(1.0, desc="Scene Complete!")
238
 
239
  except ValueError as ve:
240
  log_accumulator.append(f"\n**INPUT/CONFIG ERROR:** {ve}")
241
- ret_status_bar_html = gr.HTML(value=f"<p class='error_text status_text'>❌ CONFIGURATION ERROR: {ve}</p>")
242
- ret_latest_narrative_str = f"## Error\n{ve}"
243
  except Exception as e:
244
  log_accumulator.append(f"\n**UNEXPECTED RUNTIME ERROR:** {type(e).__name__} - {e}\n{traceback.format_exc()}")
245
- ret_status_bar_html = gr.HTML(value=f"<p class='error_text status_text'>❌ UNEXPECTED ERROR: {type(e).__name__}. Check logs.</p>")
246
- ret_latest_narrative_str = f"## Unexpected Error\n{type(e).__name__}: {e}\nSee log for details."
247
- # No `finally` block here for button updates; handled by `.then()`
248
 
249
  current_total_time = time.time() - start_time
250
  log_accumulator.append(f" Cycle ended at {time.strftime('%H:%M:%S')}. Total time: {current_total_time:.2f}s")
251
- ret_log_str = "\n".join(log_accumulator)
252
 
253
- # This is the FINAL return. It must be a tuple matching the `outputs` list of engage_button.click()
254
  return (
255
- ret_story_state,
256
- ret_gallery,
257
- ret_latest_image,
258
- gr.Markdown(value=ret_latest_narrative_str),
259
- ret_status_bar_html,
260
- gr.Markdown(value=ret_log_str)
261
  )
262
 
263
  def clear_story_state_ui_wrapper():
264
  new_story = Story()
265
  placeholder_img = create_placeholder_image("Your StoryVerse is a blank canvas...", color="#1A1A2E", text_color="#A0A0C0")
266
  cleared_gallery = [(placeholder_img, "Your StoryVerse is new and untold...")]
267
- 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!"
268
- status_msg = "<p class='processing_text status_text'>πŸ“œ Story Cleared. A fresh canvas awaits your imagination!</p>"
269
- return (new_story, cleared_gallery, None, gr.Markdown(value=initial_narrative), gr.HTML(value=status_msg), "Log Cleared. Ready for a new adventure!", "")
270
 
271
  def surprise_me_func():
272
- themes = ["Cosmic Horror", "Solarpunk Utopia", "Mythic Fantasy", "Noir Detective", "Silent Film Comedy", "Deep Sea Exploration", "Prehistoric Survival"]
273
- 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"]
274
- 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"]
275
- prompt = f"A protagonist {random.choice(actions)} {random.choice(settings)}. The overall theme is {random.choice(themes)}."
276
- style = random.choice(list(STYLE_PRESETS.keys()))
277
- artist = random.choice(["H.R. Giger", "Moebius", "Eyvind Earle", " Remedios Varo", "Alphonse Mucha", ""]*2)
278
- return prompt, style, artist
279
-
280
- # --- Functions to control button interactivity ---
281
  def disable_buttons_for_processing():
282
  return gr.Button(interactive=False), gr.Button(interactive=False)
283
 
@@ -285,15 +282,35 @@ def enable_buttons_after_processing():
285
  return gr.Button(interactive=True), gr.Button(interactive=True)
286
 
287
  # --- Gradio UI Definition ---
288
- with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨ - AI Story & World Weaver") as story_weaver_demo:
 
289
  story_state_output = gr.State(Story())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
 
291
  gr.Markdown("<div align='center'><h1>✨ StoryVerse Omega ✨</h1>\n<h3>Craft Immersive Multimodal Worlds with AI</h3></div>")
292
  gr.HTML("<div class='important-note'><strong>Welcome, Worldsmith!</strong> Describe your vision, choose your style, and let Omega help you weave captivating scenes with narrative and imagery. Ensure API keys (<code>STORYVERSE_...</code>) are correctly set in Space Secrets!</div>")
293
 
294
  with gr.Accordion("πŸ”§ AI Services Status & Info", open=False):
295
- status_text_list = []
296
- text_llm_ok, image_gen_ok = (GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY), (STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY)
297
  if not text_llm_ok and not image_gen_ok: status_text_list.append("<p style='color:#FCA5A5;font-weight:bold;'>⚠️ CRITICAL: NO AI SERVICES CONFIGURED.</p>")
298
  else:
299
  if text_llm_ok: status_text_list.append("<p style='color:#A7F3D0;'>βœ… Text Generation Service(s) Ready.</p>")
@@ -309,7 +326,7 @@ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨
309
  scene_prompt_input = gr.Textbox(lines=7, label="Scene Vision (Description, Dialogue, Action):", placeholder="e.g., Amidst swirling cosmic dust...")
310
  with gr.Row(elem_classes=["compact-row"]):
311
  with gr.Column(scale=2):
312
- image_style_input = gr.Dropdown(choices=["Default (Cinematic Realism)"] + sorted(list(STYLE_PRESETS.keys())), value="Default (Cinematic Realism)", label="Visual Style Preset")
313
  with gr.Column(scale=2):
314
  artist_style_input = gr.Textbox(label="Artistic Inspiration (Optional):", placeholder="e.g., Moebius...")
315
  negative_prompt_input = gr.Textbox(lines=2, label="Exclude from Image (Negative Prompt):", value=COMMON_NEGATIVE_PROMPTS)
@@ -339,29 +356,28 @@ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨
339
  output_interaction_log_markdown = gr.Markdown("Log will appear here...")
340
 
341
  # Chained event handling for engage_button
342
- click_event_engage = engage_button.click(
343
- fn=disable_buttons_for_processing, # Step 1: Disable buttons
344
  inputs=None,
345
  outputs=[engage_button, surprise_button],
346
- queue=False # Run immediately without queuing for this UI update
347
  ).then(
348
- fn=add_scene_to_story_orchestrator, # Step 2: Run the main orchestrator
349
  inputs=[
350
  story_state_output, scene_prompt_input,
351
  image_style_input, artist_style_input, negative_prompt_input,
352
  text_model_dropdown, image_provider_dropdown,
353
  narrative_length_dropdown, image_quality_dropdown
354
  ],
355
- outputs=[ # These are updated by the FINAL RETURN of the orchestrator
356
  story_state_output, output_gallery, output_latest_scene_image,
357
  output_latest_scene_narrative, output_status_bar, output_interaction_log_markdown
358
  ]
359
- # Progressive updates within orchestrator happen via `yield {component_var: update}`
360
- ).then(
361
- fn=enable_buttons_after_processing, # Step 3: Re-enable buttons
362
  inputs=None,
363
  outputs=[engage_button, surprise_button],
364
- queue=False # Run immediately after orchestrator finishes
365
  )
366
 
367
  clear_story_button.click(
@@ -379,10 +395,10 @@ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨
379
  outputs=[scene_prompt_input, image_style_input, artist_style_input]
380
  )
381
  gr.Examples(
382
- examples=[
383
  ["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"],
384
  ["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"],
385
- ["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"],
386
  ["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"]
387
  ],
388
  inputs=[scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input],
@@ -392,16 +408,10 @@ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨
392
 
393
  # --- Entry Point ---
394
  if __name__ == "__main__":
 
 
 
 
 
395
  print("="*80)
396
- print("✨ StoryVerse Omegaβ„’ - AI Story & World Weaver - Launching... ✨")
397
- print(f" Text LLM Ready (Gemini): {GEMINI_TEXT_IS_READY}")
398
- print(f" Text LLM Ready (HF): {HF_TEXT_IS_READY}")
399
- print(f" Image Provider Ready (Stability AI): {STABILITY_API_IS_READY}")
400
- print(f" Image Provider Ready (DALL-E): {OPENAI_DALLE_IS_READY}")
401
- if not (GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY) or not (STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY):
402
- print(" πŸ”΄ WARNING: Not all required AI services are configured correctly.")
403
- print(f" Default Text Model: {UI_DEFAULT_TEXT_MODEL_KEY}")
404
- print(f" Default Image Provider: {UI_DEFAULT_IMAGE_PROVIDER_KEY}")
405
- print("="*80)
406
-
407
  story_weaver_demo.launch(debug=True, server_name="0.0.0.0", share=False)
 
9
 
10
  # --- Core Logic Imports ---
11
  from core.llm_services import initialize_text_llms, is_gemini_text_ready, is_hf_text_ready, generate_text_gemini, generate_text_hf
12
+ from core.image_services import initialize_image_llms, STABILITY_API_CONFIGURED, OPENAI_DALLE_CONFIGURED, HF_IMAGE_API_CONFIGURED, generate_image_stabilityai, generate_image_dalle, generate_image_hf_model, ImageGenResponse
13
  from core.story_engine import Story, Scene
14
  from prompts.narrative_prompts import get_narrative_system_prompt, format_narrative_user_prompt
15
  from prompts.image_style_prompts import STYLE_PRESETS, COMMON_NEGATIVE_PROMPTS, format_image_generation_prompt
 
21
 
22
  # --- Get API Readiness Status ---
23
  GEMINI_TEXT_IS_READY = is_gemini_text_ready()
24
+ HF_TEXT_IS_READY = is_hf_text_ready() # For text models
25
  STABILITY_API_IS_READY = STABILITY_API_CONFIGURED
26
  OPENAI_DALLE_IS_READY = OPENAI_DALLE_CONFIGURED
27
+ HF_IMAGE_IS_READY = HF_IMAGE_API_CONFIGURED # For image models via HF API
28
 
29
  # --- Application Configuration (Models, Defaults) ---
30
  TEXT_MODELS = {}
 
32
  if GEMINI_TEXT_IS_READY:
33
  TEXT_MODELS["✨ Gemini 1.5 Flash (Narrate)"] = {"id": "gemini-1.5-flash-latest", "type": "gemini"}
34
  TEXT_MODELS["Legacy Gemini 1.0 Pro (Narrate)"] = {"id": "gemini-1.0-pro-latest", "type": "gemini"}
35
+ if HF_TEXT_IS_READY:
36
+ TEXT_MODELS["Mistral 7B (Narrate via HF)"] = {"id": "mistralai/Mistral-7B-Instruct-v0.2", "type": "hf_text"}
37
+ TEXT_MODELS["Gemma 2B (Narrate via HF)"] = {"id": "google/gemma-2b-it", "type": "hf_text"}
38
 
39
  if TEXT_MODELS:
40
+ if GEMINI_TEXT_IS_READY and "✨ Gemini 1.5 Flash (Narrate)" in TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = "✨ Gemini 1.5 Flash (Narrate)"
41
+ elif HF_TEXT_IS_READY and "Mistral 7B (Narrate via HF)" in TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = "Mistral 7B (Narrate via HF)"
42
+ elif TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = list(TEXT_MODELS.keys())[0]
 
 
 
 
43
  else:
44
  TEXT_MODELS["No Text Models Configured"] = {"id": "dummy_text_error", "type": "none"}
45
  UI_DEFAULT_TEXT_MODEL_KEY = "No Text Models Configured"
46
 
47
+
48
  IMAGE_PROVIDERS = {}
49
  UI_DEFAULT_IMAGE_PROVIDER_KEY = None
50
+ # Order of preference for default: Stability, then HF, then DALL-E (simulated)
51
  if STABILITY_API_IS_READY: IMAGE_PROVIDERS["🎨 Stability AI (SDXL)"] = "stability_ai"
52
+ if HF_IMAGE_IS_READY:
53
+ IMAGE_PROVIDERS["🎑 HF Model (SDXL Base)"] = "hf_image_sdxl_base" # Key for SDXL via HF
54
+ IMAGE_PROVIDERS["🎠 HF Model (OpenJourney)"] = "hf_image_openjourney" # Key for OpenJourney via HF
55
  if OPENAI_DALLE_IS_READY: IMAGE_PROVIDERS["πŸ–ΌοΈ DALL-E 3 (Sim.)"] = "dalle"
56
 
57
  if IMAGE_PROVIDERS:
58
+ if STABILITY_API_IS_READY and "🎨 Stability AI (SDXL)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "🎨 Stability AI (SDXL)"
59
+ elif HF_IMAGE_IS_READY and "🎑 HF Model (SDXL Base)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "🎑 HF Model (SDXL Base)"
60
+ elif OPENAI_DALLE_IS_READY and "πŸ–ΌοΈ DALL-E 3 (Sim.)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "πŸ–ΌοΈ DALL-E 3 (Sim.)"
61
  else: UI_DEFAULT_IMAGE_PROVIDER_KEY = list(IMAGE_PROVIDERS.keys())[0]
62
  else:
63
  IMAGE_PROVIDERS["No Image Providers Configured"] = "none"
64
  UI_DEFAULT_IMAGE_PROVIDER_KEY = "No Image Providers Configured"
65
 
66
+
67
  # --- Gradio UI Theme and CSS ---
68
  omega_theme = gr.themes.Base(
69
  font=[gr.themes.GoogleFont("Lexend Deca"), "ui-sans-serif", "system-ui", "sans-serif"],
 
85
  .output-section-header { font-size: 1.8em; font-weight: 600; color: #D0D0FF; margin-top: 15px; margin-bottom: 12px;}
86
  .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;}
87
  .gr-button { border-radius: 8px !important; font-weight: 500 !important; transition: all 0.2s ease-in-out !important;}
88
+ .gr-button-primary { padding-top: 10px !important; padding-bottom: 10px !important; } /* Ensure enough padding for icons/text */
89
  .gr-button-primary:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(127,0,255,0.3) !important; }
90
  .panel_image { border-radius: 12px !important; overflow: hidden; box-shadow: 0 6px 15px rgba(0,0,0,0.25) !important; background-color: #23233A;}
91
  .panel_image img { max-height: 600px !important; }
 
108
  #surprise_button:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(255,126,95,0.3) !important; }
109
  """
110
 
111
+ # --- Helper: Placeholder Image Creation ---
112
  def create_placeholder_image(text="Processing...", size=(512, 512), color="#23233A", text_color="#E0E0FF"):
113
+ # ... (Full implementation as before) ...
114
  img = Image.new('RGB', size, color=color); draw = ImageDraw.Draw(img)
115
  try: font_path = "arial.ttf" if os.path.exists("arial.ttf") else None
116
  except: font_path = None
 
120
  else: tw, th = draw.textsize(text, font=font)
121
  draw.text(((size[0]-tw)/2, (size[1]-th)/2), text, font=font, fill=text_color); return img
122
 
123
+
124
  # --- StoryVerse Weaver Orchestrator ---
125
  def add_scene_to_story_orchestrator(
126
  current_story_obj: Story, scene_prompt_text: str, image_style_dropdown: str, artist_style_text: str,
 
133
 
134
  log_accumulator = [f"**πŸš€ Scene {current_story_obj.current_scene_number + 1} - {time.strftime('%H:%M:%S')}**"]
135
 
136
+ # Placeholders for the final return tuple
137
  ret_story_state = current_story_obj
138
  ret_gallery = current_story_obj.get_all_scenes_for_gallery_display()
139
+ ret_latest_image = create_placeholder_image("🎨 Conjuring visuals...") # Initial placeholder for image component
140
+ ret_latest_narrative_md_obj = gr.Markdown(value=" Musing narrative...")
141
+ ret_status_bar_html_obj = gr.HTML(value=f"<p class='processing_text status_text'>🌌 Weaving Scene {current_story_obj.current_scene_number + 1}...</p>")
142
+ # Log is built up
143
 
144
+ # Initial yield for UI updates handled by the .then() chain for buttons
145
+ # This first yield is for immediate feedback on other components
146
  yield {
147
+ output_status_bar: ret_status_bar_html_obj,
148
+ output_latest_scene_image: gr.Image(value=ret_latest_image), # Show placeholder
149
+ output_latest_scene_narrative: ret_latest_narrative_md_obj,
 
150
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator))
151
  }
152
 
 
179
  narrative_text_generated = "**Narrative Error:** Selected text model not available or misconfigured."
180
  log_accumulator.append(f" Narrative: FAILED - Model '{text_model_key}' not available.")
181
 
182
+ ret_latest_narrative_str_content = f"## Scene Idea: {scene_prompt_text}\n\n{narrative_text_generated}"
183
+ ret_latest_narrative_md_obj = gr.Markdown(value=ret_latest_narrative_str_content)
184
+ yield { output_latest_scene_narrative: ret_latest_narrative_md_obj,
185
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) }
186
 
187
  # --- 2. Generate Image ---
 
198
  image_response = None
199
  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)
200
  elif selected_image_provider_type == "dalle": image_response = generate_image_dalle(full_image_prompt, quality="hd" if image_quality=="High Detail" else "standard")
201
+ elif selected_image_provider_type == "hf_image_sdxl_base": image_response = generate_image_hf_model(full_image_prompt, model_id="stabilityai/stable-diffusion-xl-base-1.0", negative_prompt=negative_prompt_text or COMMON_NEGATIVE_PROMPTS)
202
+ elif selected_image_provider_type == "hf_image_openjourney": image_response = generate_image_hf_model(full_image_prompt, model_id="prompthero/openjourney", negative_prompt=negative_prompt_text or COMMON_NEGATIVE_PROMPTS, width=512, height=512)
203
 
204
  if image_response and image_response.success:
205
  image_generated_pil = image_response.image
 
238
  # --- 4. Prepare Final Values for Return Tuple ---
239
  ret_gallery = current_story_obj.get_all_scenes_for_gallery_display()
240
  _ , latest_narr_for_display_final_str_temp = current_story_obj.get_latest_scene_details_for_display()
241
+ ret_latest_narrative_md_obj = gr.Markdown(value=latest_narr_for_display_final_str_temp)
242
 
243
  status_html_str_temp = f"<p class='error_text status_text'>Scene {current_story_obj.current_scene_number} added with errors.</p>" if final_scene_error else f"<p class='success_text status_text'>🌌 Scene {current_story_obj.current_scene_number} woven!</p>"
244
+ ret_status_bar_html_obj = gr.HTML(value=status_html_str_temp)
245
 
246
  progress(1.0, desc="Scene Complete!")
247
 
248
  except ValueError as ve:
249
  log_accumulator.append(f"\n**INPUT/CONFIG ERROR:** {ve}")
250
+ ret_status_bar_html_obj = gr.HTML(value=f"<p class='error_text status_text'>❌ CONFIGURATION ERROR: {ve}</p>")
251
+ ret_latest_narrative_md_obj = gr.Markdown(value=f"## Error\n{ve}")
252
  except Exception as e:
253
  log_accumulator.append(f"\n**UNEXPECTED RUNTIME ERROR:** {type(e).__name__} - {e}\n{traceback.format_exc()}")
254
+ ret_status_bar_html_obj = gr.HTML(value=f"<p class='error_text status_text'>❌ UNEXPECTED ERROR: {type(e).__name__}. Check logs.</p>")
255
+ ret_latest_narrative_md_obj = gr.Markdown(value=f"## Unexpected Error\n{type(e).__name__}: {e}\nSee log for details.")
256
+ # `finally` block is removed from here; button re-enabling is handled by .then()
257
 
258
  current_total_time = time.time() - start_time
259
  log_accumulator.append(f" Cycle ended at {time.strftime('%H:%M:%S')}. Total time: {current_total_time:.2f}s")
260
+ ret_log_md = gr.Markdown(value="\n".join(log_accumulator))
261
 
 
262
  return (
263
+ ret_story_state, ret_gallery, ret_latest_image,
264
+ ret_latest_narrative_md_obj, ret_status_bar_html_obj, ret_log_md
 
 
 
 
265
  )
266
 
267
  def clear_story_state_ui_wrapper():
268
  new_story = Story()
269
  placeholder_img = create_placeholder_image("Your StoryVerse is a blank canvas...", color="#1A1A2E", text_color="#A0A0C0")
270
  cleared_gallery = [(placeholder_img, "Your StoryVerse is new and untold...")]
271
+ initial_narrative = "## ✨ A New Story Begins ✨\nDescribe your first scene idea..."
272
+ status_msg = "<p class='processing_text status_text'>πŸ“œ Story Cleared.</p>"
273
+ return (new_story, cleared_gallery, None, gr.Markdown(initial_narrative), gr.HTML(status_msg), "Log Cleared.", "")
274
 
275
  def surprise_me_func():
276
+ themes = ["Cosmic Horror", "Solarpunk Utopia", "Mythic Fantasy", "Noir Detective"]; actions = ["unearths an artifact", "negotiates"]; settings = ["on a rogue planet", "in a city in a tree"]; prompt = f"A protagonist {random.choice(actions)} {random.choice(settings)}. Theme: {random.choice(themes)}."; style = random.choice(list(STYLE_PRESETS.keys())); artist = random.choice(["H.R. Giger", "Moebius", ""]*2); return prompt, style, artist
277
+
 
 
 
 
 
 
 
278
  def disable_buttons_for_processing():
279
  return gr.Button(interactive=False), gr.Button(interactive=False)
280
 
 
282
  return gr.Button(interactive=True), gr.Button(interactive=True)
283
 
284
  # --- Gradio UI Definition ---
285
+ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨") as story_weaver_demo:
286
+ # Define Python variables for UI components that will be inputs or outputs
287
  story_state_output = gr.State(Story())
288
+ # Input Column Components
289
+ scene_prompt_input = gr.Textbox()
290
+ image_style_input = gr.Dropdown()
291
+ artist_style_input = gr.Textbox()
292
+ negative_prompt_input = gr.Textbox()
293
+ text_model_dropdown = gr.Dropdown()
294
+ image_provider_dropdown = gr.Dropdown()
295
+ narrative_length_dropdown = gr.Dropdown()
296
+ image_quality_dropdown = gr.Dropdown()
297
+ # Output Column Components
298
+ output_gallery = gr.Gallery()
299
+ output_latest_scene_image = gr.Image()
300
+ output_latest_scene_narrative = gr.Markdown()
301
+ output_status_bar = gr.HTML()
302
+ output_interaction_log_markdown = gr.Markdown()
303
+ # Button components (updated via chained .then() events)
304
+ engage_button = gr.Button()
305
+ surprise_button = gr.Button()
306
+ clear_story_button = gr.Button()
307
 
308
+ # --- Layout the UI ---
309
  gr.Markdown("<div align='center'><h1>✨ StoryVerse Omega ✨</h1>\n<h3>Craft Immersive Multimodal Worlds with AI</h3></div>")
310
  gr.HTML("<div class='important-note'><strong>Welcome, Worldsmith!</strong> Describe your vision, choose your style, and let Omega help you weave captivating scenes with narrative and imagery. Ensure API keys (<code>STORYVERSE_...</code>) are correctly set in Space Secrets!</div>")
311
 
312
  with gr.Accordion("πŸ”§ AI Services Status & Info", open=False):
313
+ 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 or HF_IMAGE_IS_READY) # Added HF_IMAGE_IS_READY
 
314
  if not text_llm_ok and not image_gen_ok: status_text_list.append("<p style='color:#FCA5A5;font-weight:bold;'>⚠️ CRITICAL: NO AI SERVICES CONFIGURED.</p>")
315
  else:
316
  if text_llm_ok: status_text_list.append("<p style='color:#A7F3D0;'>βœ… Text Generation Service(s) Ready.</p>")
 
326
  scene_prompt_input = gr.Textbox(lines=7, label="Scene Vision (Description, Dialogue, Action):", placeholder="e.g., Amidst swirling cosmic dust...")
327
  with gr.Row(elem_classes=["compact-row"]):
328
  with gr.Column(scale=2):
329
+ image_style_input = gr.Dropdown(choices=["Default (Cinematic Realism)"] + sorted(list(STYLE_PRESETS.keys())), value="Default (Cinematic Realism)", label="Visual Style Preset", allow_custom_value=True) # allow_custom_value for examples
330
  with gr.Column(scale=2):
331
  artist_style_input = gr.Textbox(label="Artistic Inspiration (Optional):", placeholder="e.g., Moebius...")
332
  negative_prompt_input = gr.Textbox(lines=2, label="Exclude from Image (Negative Prompt):", value=COMMON_NEGATIVE_PROMPTS)
 
356
  output_interaction_log_markdown = gr.Markdown("Log will appear here...")
357
 
358
  # Chained event handling for engage_button
359
+ engage_button.click(
360
+ fn=disable_buttons_for_processing,
361
  inputs=None,
362
  outputs=[engage_button, surprise_button],
363
+ queue=False
364
  ).then(
365
+ fn=add_scene_to_story_orchestrator,
366
  inputs=[
367
  story_state_output, scene_prompt_input,
368
  image_style_input, artist_style_input, negative_prompt_input,
369
  text_model_dropdown, image_provider_dropdown,
370
  narrative_length_dropdown, image_quality_dropdown
371
  ],
372
+ outputs=[
373
  story_state_output, output_gallery, output_latest_scene_image,
374
  output_latest_scene_narrative, output_status_bar, output_interaction_log_markdown
375
  ]
376
+ ).then(
377
+ fn=enable_buttons_after_processing,
 
378
  inputs=None,
379
  outputs=[engage_button, surprise_button],
380
+ queue=False
381
  )
382
 
383
  clear_story_button.click(
 
395
  outputs=[scene_prompt_input, image_style_input, artist_style_input]
396
  )
397
  gr.Examples(
398
+ examples=[ # Ensure these styles are in your STYLE_PRESETS in image_style_prompts.py
399
  ["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"],
400
  ["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"],
401
+ ["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"],
402
  ["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"]
403
  ],
404
  inputs=[scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input],
 
408
 
409
  # --- Entry Point ---
410
  if __name__ == "__main__":
411
+ print("="*80); print("✨ StoryVerse Omegaβ„’ Launching... ✨")
412
+ print(f" Gemini Text Ready: {GEMINI_TEXT_IS_READY}"); print(f" HF Text Ready: {HF_TEXT_IS_READY}")
413
+ print(f" Stability AI Ready: {STABILITY_API_IS_READY}"); print(f" DALL-E Ready: {OPENAI_DALLE_IS_READY}"); print(f" HF Image Ready: {HF_IMAGE_IS_READY}")
414
+ if not (GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY) or not (STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY or HF_IMAGE_IS_READY): print(" πŸ”΄ WARNING: Not all services configured.")
415
+ print(f" Default Text Model: {UI_DEFAULT_TEXT_MODEL_KEY}"); print(f" Default Image Provider: {UI_DEFAULT_IMAGE_PROVIDER_KEY}")
416
  print("="*80)
 
 
 
 
 
 
 
 
 
 
 
417
  story_weaver_demo.launch(debug=True, server_name="0.0.0.0", share=False)