mgbam commited on
Commit
db01582
Β·
verified Β·
1 Parent(s): fa7abf4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -85
app.py CHANGED
@@ -2,10 +2,10 @@
2
  import gradio as gr
3
  import os
4
  import time
5
- import json
6
- from PIL import Image, ImageDraw, ImageFont
7
  import random
8
- import traceback # For more detailed error logging in wrapper
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
@@ -67,18 +67,47 @@ omega_theme = gr.themes.Base(
67
  button_primary_text_color="white", button_secondary_background_fill="#4A4A6A",
68
  button_secondary_text_color="#E0E0FF", slider_color="#A020F0"
69
  )
70
- omega_css = """ /* ... (Your full omega_css string from the "WOW UI" version) ... */ """
71
- # For brevity, I'll assume the full omega_css string is here. Copy it from the previous response.
72
- # Example start: body, .gradio-container { background-color: #0F0F1A !important; ... }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  # --- Helper: Placeholder Image Creation ---
75
  def create_placeholder_image(text="Processing...", size=(512, 512), color="#23233A", text_color="#E0E0FF"):
76
- # ... (Full implementation from previous "WOW UI" version) ...
77
  img = Image.new('RGB', size, color=color); draw = ImageDraw.Draw(img)
78
- try: font = ImageFont.truetype("arial.ttf", 40) if os.path.exists("arial.ttf") else ImageFont.load_default()
 
 
79
  except IOError: font = ImageFont.load_default()
80
  if hasattr(draw, 'textbbox'): bbox = draw.textbbox((0,0), text, font=font); tw, th = bbox[2]-bbox[0], bbox[3]-bbox[1]
81
- else: tw, th = draw.textsize(text, font=font) # Fallback
82
  draw.text(((size[0]-tw)/2, (size[1]-th)/2), text, font=font, fill=text_color); return img
83
 
84
  # --- StoryVerse Weaver Orchestrator ---
@@ -88,50 +117,64 @@ def add_scene_to_story_orchestrator(
88
  narrative_length: str, image_quality: str,
89
  progress=gr.Progress(track_tqdm=True)
90
  ):
91
- # --- Define UI components for yielding (must match those in `with gr.Blocks...`) ---
92
- # This mapping is conceptual if you yield component objects directly.
93
- # If yielding dicts with string keys (like elem_id), ensure they match.
94
- # For direct object yield:
95
- # ui_status = output_status_bar
96
- # ui_latest_img = output_latest_scene_image
97
- # ... etc.
98
-
99
- if not current_story_obj: current_story_obj = Story()
100
 
101
  log_accumulator = [f"**πŸš€ Scene {current_story_obj.current_scene_number + 1} - {time.strftime('%H:%M:%S')}**"]
102
- # Initial UI update using direct component updates in yield
 
103
  yield {
104
  output_status_bar: gr.HTML(value=f"<p class='processing_text status_text'>🌌 Weaving Scene {current_story_obj.current_scene_number + 1}...</p>"),
105
  output_latest_scene_image: gr.Image(value=create_placeholder_image("🎨 Conjuring visuals..."), visible=True),
106
  output_latest_scene_narrative: gr.Markdown(value=" Musing narrative...", visible=True),
107
  engage_button: gr.Button(interactive=False),
108
  surprise_button: gr.Button(interactive=False),
109
- output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) # Initial log
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
 
112
  try:
 
 
113
  if not scene_prompt_text.strip():
114
  raise ValueError("Scene prompt cannot be empty!")
115
 
116
  # --- 1. Generate Narrative Text ---
117
  progress(0.1, desc="✍️ Crafting narrative...")
118
- narrative_text_generated = f"Narrative Error: Generation failed for '{scene_prompt_text[:30]}...'." # Default error
119
  text_model_info = TEXT_MODELS.get(text_model_key)
120
  if text_model_info and text_model_info["type"] != "none":
121
- # ... (Full narrative generation logic as in your last working add_scene_to_story) ...
122
- system_p = get_narrative_system_prompt("default")
123
- prev_narrative = current_story_obj.get_last_scene_narrative()
124
  user_p = format_narrative_user_prompt(scene_prompt_text, prev_narrative)
125
  log_accumulator.append(f" Narrative: Using {text_model_key} ({text_model_info['id']}). Length aim: {narrative_length}")
126
  text_response = None
127
  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)
128
  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)
129
- if text_response and text_response.success: narrative_text_generated = basic_text_cleanup(text_response.text); log_accumulator.append(f" Narrative: Success.")
130
- elif text_response: narrative_text_generated = f"**Narrative Error ({text_model_key}):** {text_response.error}"; log_accumulator.append(f" Narrative: FAILED - {text_response.error}")
131
- else: log_accumulator.append(f" Narrative: FAILED - No response from {text_model_key}.")
132
- else: narrative_text_generated = "**Narrative Error:** Text model unavailable."; log_accumulator.append(f" Narrative: FAILED - Model '{text_model_key}' unavailable.")
 
 
 
 
 
 
 
 
133
 
134
- yield { output_latest_scene_narrative: gr.Markdown(value=f"## Scene Idea: {scene_prompt_text}\n\n{narrative_text_generated}"),
 
135
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) }
136
 
137
  # --- 2. Generate Image ---
@@ -145,25 +188,33 @@ def add_scene_to_story_orchestrator(
145
  log_accumulator.append(f" Image: Using {image_provider_key}. Style: {image_style_dropdown}. Artist: {artist_style_text or 'N/A'}.")
146
 
147
  if selected_image_provider_type and selected_image_provider_type != "none":
148
- # ... (Full image generation logic as in your last working add_scene_to_story) ...
149
  image_response = None
150
  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)
151
  elif selected_image_provider_type == "dalle": image_response = generate_image_dalle(full_image_prompt, quality="hd" if image_quality=="High Detail" else "standard")
152
- if image_response and image_response.success: image_generated_pil = image_response.image; log_accumulator.append(f" Image: Success.")
153
- 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}")
154
- else: image_generation_error_message = f"**Image Error:** No response from {image_provider_key}."; log_accumulator.append(f" Image: FAILED - No response.")
155
- else: image_generation_error_message = "**Image Error:** Image provider unavailable."; log_accumulator.append(f" Image: FAILED - Provider '{image_provider_key}' unavailable.")
 
 
 
 
 
 
 
 
 
156
 
157
- yield { output_latest_scene_image: gr.Image(value=image_generated_pil if image_generated_pil else create_placeholder_image("Image Gen Failed", color="#401010")),
 
158
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) }
159
 
160
  # --- 3. Add Scene to Story Object ---
161
  final_scene_error = None
162
- # ... (Logic to set final_scene_error based on narrative and image errors) ...
163
  if image_generation_error_message and "**Narrative Error**" in narrative_text_generated : final_scene_error = f"{narrative_text_generated}\n{image_generation_error_message}"
164
  elif "**Narrative Error**" in narrative_text_generated: final_scene_error = narrative_text_generated
165
  elif image_generation_error_message: final_scene_error = image_generation_error_message
166
-
167
  current_story_obj.add_scene_from_elements(
168
  user_prompt=scene_prompt_text,
169
  narrative_text=narrative_text_generated if "**Narrative Error**" not in narrative_text_generated else "(Narrative gen failed)",
@@ -172,41 +223,40 @@ def add_scene_to_story_orchestrator(
172
  image_provider=image_provider_key if selected_image_provider_type != "none" else "N/A",
173
  error_message=final_scene_error
174
  )
175
- log_accumulator.append(f" Scene {current_story_obj.current_scene_number} processed.")
 
176
 
177
  # --- 4. Prepare Final Outputs for Gradio ---
178
- gallery_items_tuples = current_story_obj.get_all_scenes_for_gallery_display()
179
- _ , latest_narr_for_display_final = current_story_obj.get_latest_scene_details_for_display()
180
 
181
- status_message_html = 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>"
182
 
183
  progress(1.0, desc="Scene Complete!")
184
 
185
- # Final return for the .click() handler's `outputs` list
186
- return (
187
- current_story_obj,
188
- gallery_items_tuples,
189
- image_generated_pil if image_generated_pil else create_placeholder_image("No Image" if not final_scene_error else "Image Error", color="#23233A"),
190
- latest_narr_for_display_final,
191
- gr.HTML(value=status_message_html),
192
- "\n".join(log_accumulator)
193
- )
194
-
195
- except ValueError as ve: # Catch our specific input/config errors
196
  log_accumulator.append(f"\n**INPUT/CONFIG ERROR:** {ve}")
197
- status_html_err = f"<p style='color: red;'>❌ CONFIGURATION ERROR: {ve}</p>"
198
- # Return values for all outputs in case of error to prevent None issues
199
- return current_story_obj, current_story_obj.get_all_scenes_for_gallery_display(), None, f"## Error\n{ve}", gr.HTML(value=status_html_err), "\n".join(log_accumulator)
200
  except Exception as e:
201
  log_accumulator.append(f"\n**UNEXPECTED RUNTIME ERROR:** {type(e).__name__} - {e}\n{traceback.format_exc()}")
202
- status_html_err_unexp = f"<p style='color: red;'>❌ UNEXPECTED ERROR: {e}. Check logs.</p>"
203
- return current_story_obj, current_story_obj.get_all_scenes_for_gallery_display(), None, "## Unexpected Error\nSee log for details.", gr.HTML(value=status_html_err_unexp), "\n".join(log_accumulator)
204
- finally: # Ensure buttons are re-enabled even if an error occurs mid-yield
205
- # This yield will be the last one if no error, or one of the last if error happened
206
- # However, the final `return` is what matters for the outputs list of .click()
207
- # We can also yield here to ensure buttons are re-enabled after processing.
208
  yield { engage_button: gr.Button(interactive=True), surprise_button: gr.Button(interactive=True) }
209
 
 
 
 
 
 
 
 
 
 
210
 
211
  def clear_story_state_ui_wrapper():
212
  new_story = Story()
@@ -214,34 +264,21 @@ def clear_story_state_ui_wrapper():
214
  cleared_gallery = [(placeholder_img, "Your StoryVerse is new and untold...")]
215
  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!"
216
  status_msg = "<p class='processing_text status_text'>πŸ“œ Story Cleared. A fresh canvas awaits your imagination!</p>"
217
-
218
- # Return a TUPLE in the order of the .click() outputs list
219
- return (
220
- new_story,
221
- cleared_gallery,
222
- None, # For latest_scene_image_output
223
- gr.Markdown(value=initial_narrative), # For latest_scene_narrative_output
224
- gr.HTML(value=status_msg), # For output_status_bar
225
- "Log Cleared. Ready for a new adventure!", # For output_interaction_log_markdown
226
- "" # For scene_prompt_input (to clear it)
227
- )
228
 
229
  def surprise_me_func():
230
- # ... (Same surprise_me_func as before)
231
- themes = ["Cosmic Horror", "Solarpunk Utopia", "Mythic Fantasy", "Noir Detective", "Silent Film Comedy"]
232
- 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"]
233
- 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"]
234
  prompt = f"A protagonist {random.choice(actions)} {random.choice(settings)}. The overall theme is {random.choice(themes)}."
235
  style = random.choice(list(STYLE_PRESETS.keys()))
236
- artist = random.choice(["H.R. Giger", "Moebius", "Eyvind Earle", " Remedios Varo", ""]*2)
237
  return prompt, style, artist
238
 
239
-
240
  # --- Gradio UI Definition ---
241
  with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨ - AI Story & World Weaver") as story_weaver_demo:
242
  # Define output components that will be updated by the orchestrator
243
- # story_state_output will be a gr.State component
244
- story_state_output = gr.State(Story()) # Initial value
245
 
246
  gr.Markdown("<div align='center'><h1>✨ StoryVerse Omega ✨</h1>\n<h3>Craft Immersive Multimodal Worlds with AI</h3></div>")
247
  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>")
@@ -280,6 +317,7 @@ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨
280
  image_quality_dropdown = gr.Dropdown(["Standard", "High Detail", "Sketch Concept"], value="Standard", label="Image Detail/Style")
281
 
282
  with gr.Row(elem_classes=["compact-row"], equal_height=True):
 
283
  engage_button = gr.Button("🌌 Weave This Scene!", variant="primary", scale=3, icon="✨")
284
  surprise_button = gr.Button("🎲 Surprise Me!", variant="secondary", scale=1, icon="🎁")
285
  clear_story_button = gr.Button("πŸ—‘οΈ New Story", variant="stop", scale=1, icon="♻️")
@@ -316,7 +354,6 @@ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨
316
  output_status_bar,
317
  output_interaction_log_markdown
318
  ]
319
- # Progressive updates are handled by `yield {component_variable: new_value}` inside the orchestrator
320
  )
321
  clear_story_button.click(
322
  fn=clear_story_state_ui_wrapper,
@@ -338,15 +375,13 @@ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨
338
  examples=[
339
  ["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"],
340
  ["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"],
341
- ["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"],
342
- ["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"]
343
  ],
344
  inputs=[scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input],
345
  label="🌌 Example Universes to Weave 🌌",
346
  )
347
  gr.HTML("<div style='text-align:center; margin-top:30px; padding-bottom:20px;'><p style='font-size:0.9em; color:#8080A0;'>✨ StoryVerse Omegaβ„’ - Weaving Worlds with Words and Pixels ✨</p></div>")
348
 
349
- # --- Entry Point for Running the Gradio App ---
350
  if __name__ == "__main__":
351
  print("="*80)
352
  print("✨ StoryVerse Omegaβ„’ - AI Story & World Weaver - Launching... ✨")
@@ -355,7 +390,7 @@ if __name__ == "__main__":
355
  print(f" Image Provider Ready (Stability AI): {STABILITY_API_IS_READY}")
356
  print(f" Image Provider Ready (DALL-E): {OPENAI_DALLE_IS_READY}")
357
  if not (GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY) or not (STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY):
358
- print(" πŸ”΄ WARNING: Not all required AI services are configured correctly. Functionality will be severely limited or fail.")
359
  print(f" Default Text Model: {UI_DEFAULT_TEXT_MODEL_KEY}")
360
  print(f" Default Image Provider: {UI_DEFAULT_IMAGE_PROVIDER_KEY}")
361
  print("="*80)
 
2
  import gradio as gr
3
  import os
4
  import time
5
+ import json
6
+ from PIL import Image, ImageDraw, ImageFont
7
  import random
8
+ 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
 
67
  button_primary_text_color="white", button_secondary_background_fill="#4A4A6A",
68
  button_secondary_text_color="#E0E0FF", slider_color="#A020F0"
69
  )
70
+ omega_css = """
71
+ body, .gradio-container { background-color: #0F0F1A !important; color: #D0D0E0 !important; }
72
+ .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;}
73
+ .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);}
74
+ .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;}
75
+ .gr-markdown h3 { color: #C080F0 !important; text-align: center; font-weight: 400; margin-bottom: 25px !important;}
76
+ .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;}
77
+ .output-section-header { font-size: 1.8em; font-weight: 600; color: #D0D0FF; margin-top: 15px; margin-bottom: 12px;}
78
+ .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;}
79
+ .gr-button { border-radius: 8px !important; font-weight: 500 !important; transition: all 0.2s ease-in-out !important;}
80
+ .gr-button-primary:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(127,0,255,0.3) !important; }
81
+ .panel_image { border-radius: 12px !important; overflow: hidden; box-shadow: 0 6px 15px rgba(0,0,0,0.25) !important; background-color: #23233A;}
82
+ .panel_image img { max-height: 600px !important; }
83
+ .gallery_output { background-color: transparent !important; border: none !important; }
84
+ .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;}
85
+ .gallery_output .thumbnail-item:hover { transform: scale(1.05); }
86
+ .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;}
87
+ .error_text { background-color: #401010 !important; color: #FFB0B0 !important; border-color: #802020 !important; }
88
+ .success_text { background-color: #104010 !important; color: #B0FFB0 !important; border-color: #208020 !important;}
89
+ .processing_text { background-color: #102040 !important; color: #B0D0FF !important; border-color: #204080 !important;}
90
+ .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;}
91
+ .gr-tabitem { background-color: #1A1A2E !important; border-radius: 0 0 12px 12px !important; padding: 15px !important;}
92
+ .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;}
93
+ .gr-tab-button { color: #A0A0C0 !important; border-radius: 8px 8px 0 0 !important;}
94
+ .gr-accordion > .gr-block { border-top: 1px solid #2A2A4A !important; }
95
+ .gr-markdown code { background-color: #2A2A4A !important; color: #C0C0E0 !important; padding: 0.2em 0.5em; border-radius: 4px; }
96
+ .gr-markdown pre { background-color: #23233A !important; padding: 1em !important; border-radius: 6px !important; border: 1px solid #2A2A4A !important;}
97
+ .gr-markdown pre > code { padding: 0 !important; background-color: transparent !important; }
98
+ #surprise_button { background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%) !important; font-weight:600 !important;}
99
+ #surprise_button:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(255,126,95,0.3) !important; }
100
+ """
101
 
102
  # --- Helper: Placeholder Image Creation ---
103
  def create_placeholder_image(text="Processing...", size=(512, 512), color="#23233A", text_color="#E0E0FF"):
 
104
  img = Image.new('RGB', size, color=color); draw = ImageDraw.Draw(img)
105
+ try: font_path = "arial.ttf" if os.path.exists("arial.ttf") else None
106
+ except: font_path = None # Handle potential OS errors accessing font list
107
+ try: font = ImageFont.truetype(font_path, 40) if font_path else ImageFont.load_default()
108
  except IOError: font = ImageFont.load_default()
109
  if hasattr(draw, 'textbbox'): bbox = draw.textbbox((0,0), text, font=font); tw, th = bbox[2]-bbox[0], bbox[3]-bbox[1]
110
+ else: tw, th = draw.textsize(text, font=font)
111
  draw.text(((size[0]-tw)/2, (size[1]-th)/2), text, font=font, fill=text_color); return img
112
 
113
  # --- StoryVerse Weaver Orchestrator ---
 
117
  narrative_length: str, image_quality: str,
118
  progress=gr.Progress(track_tqdm=True)
119
  ):
120
+ if not current_story_obj: current_story_obj = Story()
 
 
 
 
 
 
 
 
121
 
122
  log_accumulator = [f"**πŸš€ Scene {current_story_obj.current_scene_number + 1} - {time.strftime('%H:%M:%S')}**"]
123
+
124
+ # Initial UI update using direct component updates in yield by component variable name
125
  yield {
126
  output_status_bar: gr.HTML(value=f"<p class='processing_text status_text'>🌌 Weaving Scene {current_story_obj.current_scene_number + 1}...</p>"),
127
  output_latest_scene_image: gr.Image(value=create_placeholder_image("🎨 Conjuring visuals..."), visible=True),
128
  output_latest_scene_narrative: gr.Markdown(value=" Musing narrative...", visible=True),
129
  engage_button: gr.Button(interactive=False),
130
  surprise_button: gr.Button(interactive=False),
131
+ output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator))
132
+ }
133
+
134
+ # Prepare a dictionary to hold final return values for the .click() outputs list
135
+ # Initialize with current state or placeholders
136
+ final_return_dict = {
137
+ "story_state": current_story_obj,
138
+ "gallery": current_story_obj.get_all_scenes_for_gallery_display(), # Initial gallery
139
+ "latest_image": create_placeholder_image("Pending..."),
140
+ "latest_narrative": "## Pending...\nNarrative generation in progress.",
141
+ "status_bar": "<p class='processing_text status_text'>Processing...</p>",
142
+ "log": "\n".join(log_accumulator)
143
  }
144
 
145
  try:
146
+ start_time = time.time()
147
+
148
  if not scene_prompt_text.strip():
149
  raise ValueError("Scene prompt cannot be empty!")
150
 
151
  # --- 1. Generate Narrative Text ---
152
  progress(0.1, desc="✍️ Crafting narrative...")
153
+ narrative_text_generated = f"Narrative Error: Generation failed for '{scene_prompt_text[:30]}...'."
154
  text_model_info = TEXT_MODELS.get(text_model_key)
155
  if text_model_info and text_model_info["type"] != "none":
156
+ system_p = get_narrative_system_prompt("default")
157
+ prev_narrative = current_story_obj.get_last_scene_narrative()
 
158
  user_p = format_narrative_user_prompt(scene_prompt_text, prev_narrative)
159
  log_accumulator.append(f" Narrative: Using {text_model_key} ({text_model_info['id']}). Length aim: {narrative_length}")
160
  text_response = None
161
  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)
162
  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)
163
+
164
+ if text_response and text_response.success:
165
+ narrative_text_generated = basic_text_cleanup(text_response.text)
166
+ log_accumulator.append(f" Narrative: Success. (Snippet: {narrative_text_generated[:50]}...)")
167
+ elif text_response:
168
+ narrative_text_generated = f"**Narrative Error ({text_model_key}):** {text_response.error}"
169
+ log_accumulator.append(f" Narrative: FAILED - {text_response.error}")
170
+ else:
171
+ log_accumulator.append(f" Narrative: FAILED - No response object from {text_model_key}.")
172
+ else:
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
+ final_return_dict["latest_narrative"] = f"## Scene Idea: {scene_prompt_text}\n\n{narrative_text_generated}"
177
+ yield { output_latest_scene_narrative: gr.Markdown(value=final_return_dict["latest_narrative"]),
178
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) }
179
 
180
  # --- 2. Generate Image ---
 
188
  log_accumulator.append(f" Image: Using {image_provider_key}. Style: {image_style_dropdown}. Artist: {artist_style_text or 'N/A'}.")
189
 
190
  if selected_image_provider_type and selected_image_provider_type != "none":
 
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
197
+ log_accumulator.append(f" Image: Success from {image_response.provider}.")
198
+ elif image_response:
199
+ image_generation_error_message = f"**Image Error ({image_response.provider}):** {image_response.error}"
200
+ log_accumulator.append(f" Image: FAILED - {image_response.error}")
201
+ else:
202
+ image_generation_error_message = f"**Image Error:** No response object from {image_provider_key} service."
203
+ log_accumulator.append(f" Image: FAILED - No response object from {image_provider_key}.")
204
+ else:
205
+ image_generation_error_message = "**Image Error:** Selected image provider not available or misconfigured."
206
+ log_accumulator.append(f" Image: FAILED - Provider '{image_provider_key}' not available.")
207
 
208
+ final_return_dict["latest_image"] = image_generated_pil if image_generated_pil else create_placeholder_image("Image Gen Failed", color="#401010")
209
+ yield { output_latest_scene_image: gr.Image(value=final_return_dict["latest_image"]),
210
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) }
211
 
212
  # --- 3. Add Scene to Story Object ---
213
  final_scene_error = None
 
214
  if image_generation_error_message and "**Narrative Error**" in narrative_text_generated : final_scene_error = f"{narrative_text_generated}\n{image_generation_error_message}"
215
  elif "**Narrative Error**" in narrative_text_generated: final_scene_error = narrative_text_generated
216
  elif image_generation_error_message: final_scene_error = image_generation_error_message
217
+
218
  current_story_obj.add_scene_from_elements(
219
  user_prompt=scene_prompt_text,
220
  narrative_text=narrative_text_generated if "**Narrative Error**" not in narrative_text_generated else "(Narrative gen failed)",
 
223
  image_provider=image_provider_key if selected_image_provider_type != "none" else "N/A",
224
  error_message=final_scene_error
225
  )
226
+ final_return_dict["story_state"] = current_story_obj # Update story state in final dict
227
+ log_accumulator.append(f" Scene {current_story_obj.current_scene_number} processed and added to story object.")
228
 
229
  # --- 4. Prepare Final Outputs for Gradio ---
230
+ final_return_dict["gallery"] = current_story_obj.get_all_scenes_for_gallery_display()
231
+ _ , final_return_dict["latest_narrative"] = current_story_obj.get_latest_scene_details_for_display() # Update narrative for final return
232
 
233
+ final_return_dict["status_bar"] = 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>"
234
 
235
  progress(1.0, desc="Scene Complete!")
236
 
237
+ except ValueError as ve:
 
 
 
 
 
 
 
 
 
 
238
  log_accumulator.append(f"\n**INPUT/CONFIG ERROR:** {ve}")
239
+ final_return_dict["status_bar"] = f"<p class='error_text status_text'>❌ CONFIGURATION ERROR: {ve}</p>"
240
+ final_return_dict["latest_narrative"] = f"## Error\n{ve}"
 
241
  except Exception as e:
242
  log_accumulator.append(f"\n**UNEXPECTED RUNTIME ERROR:** {type(e).__name__} - {e}\n{traceback.format_exc()}")
243
+ final_return_dict["status_bar"] = f"<p class='error_text status_text'>❌ UNEXPECTED ERROR: {e}. Check logs.</p>"
244
+ final_return_dict["latest_narrative"] = f"## Unexpected Error\n{e}\nSee log for details."
245
+ finally:
246
+ log_accumulator.append(f" Cycle ended at {time.strftime('%H:%M:%S')}. Total time: {time.time() - (start_time if 'start_time' in locals() else time.time()):.2f}s")
247
+ final_return_dict["log"] = "\n".join(log_accumulator)
248
+ # Final yield to re-enable buttons, this will be overridden by the return for other components
249
  yield { engage_button: gr.Button(interactive=True), surprise_button: gr.Button(interactive=True) }
250
 
251
+ # Final return statement for the .click() handler
252
+ return (
253
+ final_return_dict["story_state"],
254
+ final_return_dict["gallery"],
255
+ final_return_dict["latest_image"],
256
+ gr.Markdown(value=final_return_dict["latest_narrative"]),
257
+ gr.HTML(value=final_return_dict["status_bar"]),
258
+ gr.Markdown(value=final_return_dict["log"])
259
+ )
260
 
261
  def clear_story_state_ui_wrapper():
262
  new_story = Story()
 
264
  cleared_gallery = [(placeholder_img, "Your StoryVerse is new and untold...")]
265
  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!"
266
  status_msg = "<p class='processing_text status_text'>πŸ“œ Story Cleared. A fresh canvas awaits your imagination!</p>"
267
+ return (new_story, cleared_gallery, None, gr.Markdown(value=initial_narrative), gr.HTML(value=status_msg), "Log Cleared. Ready for a new adventure!", "")
 
 
 
 
 
 
 
 
 
 
268
 
269
  def surprise_me_func():
270
+ themes = ["Cosmic Horror", "Solarpunk Utopia", "Mythic Fantasy", "Noir Detective", "Silent Film Comedy", "Deep Sea Exploration", "Prehistoric Survival"]
271
+ 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"]
272
+ 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"]
 
273
  prompt = f"A protagonist {random.choice(actions)} {random.choice(settings)}. The overall theme is {random.choice(themes)}."
274
  style = random.choice(list(STYLE_PRESETS.keys()))
275
+ artist = random.choice(["H.R. Giger", "Moebius", "Eyvind Earle", " Remedios Varo", "Alphonse Mucha", ""]*2)
276
  return prompt, style, artist
277
 
 
278
  # --- Gradio UI Definition ---
279
  with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨ - AI Story & World Weaver") as story_weaver_demo:
280
  # Define output components that will be updated by the orchestrator
281
+ story_state_output = gr.State(Story())
 
282
 
283
  gr.Markdown("<div align='center'><h1>✨ StoryVerse Omega ✨</h1>\n<h3>Craft Immersive Multimodal Worlds with AI</h3></div>")
284
  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>")
 
317
  image_quality_dropdown = gr.Dropdown(["Standard", "High Detail", "Sketch Concept"], value="Standard", label="Image Detail/Style")
318
 
319
  with gr.Row(elem_classes=["compact-row"], equal_height=True):
320
+ # Assign these buttons to Python variables to be updated by yield
321
  engage_button = gr.Button("🌌 Weave This Scene!", variant="primary", scale=3, icon="✨")
322
  surprise_button = gr.Button("🎲 Surprise Me!", variant="secondary", scale=1, icon="🎁")
323
  clear_story_button = gr.Button("πŸ—‘οΈ New Story", variant="stop", scale=1, icon="♻️")
 
354
  output_status_bar,
355
  output_interaction_log_markdown
356
  ]
 
357
  )
358
  clear_story_button.click(
359
  fn=clear_story_state_ui_wrapper,
 
375
  examples=[
376
  ["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"],
377
  ["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"],
 
 
378
  ],
379
  inputs=[scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input],
380
  label="🌌 Example Universes to Weave 🌌",
381
  )
382
  gr.HTML("<div style='text-align:center; margin-top:30px; padding-bottom:20px;'><p style='font-size:0.9em; color:#8080A0;'>✨ StoryVerse Omegaβ„’ - Weaving Worlds with Words and Pixels ✨</p></div>")
383
 
384
+ # --- Entry Point ---
385
  if __name__ == "__main__":
386
  print("="*80)
387
  print("✨ StoryVerse Omegaβ„’ - AI Story & World Weaver - Launching... ✨")
 
390
  print(f" Image Provider Ready (Stability AI): {STABILITY_API_IS_READY}")
391
  print(f" Image Provider Ready (DALL-E): {OPENAI_DALLE_IS_READY}")
392
  if not (GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY) or not (STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY):
393
+ print(" πŸ”΄ WARNING: Not all required AI services are configured correctly.")
394
  print(f" Default Text Model: {UI_DEFAULT_TEXT_MODEL_KEY}")
395
  print(f" Default Image Provider: {UI_DEFAULT_IMAGE_PROVIDER_KEY}")
396
  print("="*80)