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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -94
app.py CHANGED
@@ -9,7 +9,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
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
@@ -17,21 +18,18 @@ from core.utils import basic_text_cleanup
17
 
18
  # --- Initialize Services ---
19
  initialize_text_llms()
20
- 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() # 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 = {}
31
  UI_DEFAULT_TEXT_MODEL_KEY = None
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"}
@@ -39,29 +37,21 @@ if HF_TEXT_IS_READY:
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 ---
@@ -85,7 +75,7 @@ body, .gradio-container { background-color: #0F0F1A !important; color: #D0D0E0 !
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; }
@@ -110,7 +100,6 @@ body, .gradio-container { background-color: #0F0F1A !important; color: #D0D0E0 !
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,7 +109,6 @@ def create_placeholder_image(text="Processing...", size=(512, 512), color="#2323
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,20 +121,18 @@ def add_scene_to_story_orchestrator(
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
 
@@ -166,21 +152,13 @@ def add_scene_to_story_orchestrator(
166
  text_response = None
167
  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)
168
  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)
169
-
170
- if text_response and text_response.success:
171
- narrative_text_generated = basic_text_cleanup(text_response.text)
172
- log_accumulator.append(f" Narrative: Success. (Snippet: {narrative_text_generated[:50]}...)")
173
- elif text_response:
174
- narrative_text_generated = f"**Narrative Error ({text_model_key}):** {text_response.error}"
175
- log_accumulator.append(f" Narrative: FAILED - {text_response.error}")
176
- else:
177
- log_accumulator.append(f" Narrative: FAILED - No response object from {text_model_key}.")
178
- else:
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
 
@@ -188,31 +166,51 @@ def add_scene_to_story_orchestrator(
188
  progress(0.5, desc="🎨 Conjuring visuals...")
189
  image_generated_pil = None
190
  image_generation_error_message = None
191
- selected_image_provider_type = IMAGE_PROVIDERS.get(image_provider_key)
 
 
192
  image_content_prompt_for_gen = narrative_text_generated if narrative_text_generated and "Error" not in narrative_text_generated else scene_prompt_text
193
  quality_keyword = "ultra detailed, intricate, masterpiece, " if image_quality == "High Detail" else ("concept sketch, line art, " if image_quality == "Sketch Concept" else "")
194
  full_image_prompt = format_image_generation_prompt(quality_keyword + image_content_prompt_for_gen[:350], image_style_dropdown, artist_style_text)
195
- log_accumulator.append(f" Image: Using {image_provider_key}. Style: {image_style_dropdown}. Artist: {artist_style_text or 'N/A'}.")
196
 
197
  if selected_image_provider_type and selected_image_provider_type != "none":
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
206
- log_accumulator.append(f" Image: Success from {image_response.provider}.")
207
- elif image_response:
208
- image_generation_error_message = f"**Image Error ({image_response.provider}):** {image_response.error}"
209
- log_accumulator.append(f" Image: FAILED - {image_response.error}")
210
  else:
211
- image_generation_error_message = f"**Image Error:** No response object from {image_provider_key} service."
212
- log_accumulator.append(f" Image: FAILED - No response object from {image_provider_key}.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  else:
214
- image_generation_error_message = "**Image Error:** Selected image provider not available or misconfigured."
215
- log_accumulator.append(f" Image: FAILED - Provider '{image_provider_key}' unavailable.")
216
 
217
  ret_latest_image = image_generated_pil if image_generated_pil else create_placeholder_image("Image Gen Failed", color="#401010")
218
  yield { output_latest_scene_image: gr.Image(value=ret_latest_image),
@@ -229,7 +227,7 @@ def add_scene_to_story_orchestrator(
229
  narrative_text=narrative_text_generated if "**Narrative Error**" not in narrative_text_generated else "(Narrative gen failed)",
230
  image=image_generated_pil,
231
  image_style_prompt=f"{image_style_dropdown}{f', by {artist_style_text}' if artist_style_text and artist_style_text.strip() else ''}",
232
- image_provider=image_provider_key if selected_image_provider_type != "none" else "N/A",
233
  error_message=final_scene_error
234
  )
235
  ret_story_state = current_story_obj
@@ -253,15 +251,20 @@ def add_scene_to_story_orchestrator(
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():
@@ -283,34 +286,13 @@ def enable_buttons_after_processing():
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,7 +308,7 @@ with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨
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)
 
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, is_hf_image_api_ready, generate_image_hf_model, ImageGenResponse
13
+ # Removed STABILITY_API_CONFIGURED, OPENAI_DALLE_CONFIGURED, generate_image_stabilityai, generate_image_dalle
14
  from core.story_engine import Story, Scene
15
  from prompts.narrative_prompts import get_narrative_system_prompt, format_narrative_user_prompt
16
  from prompts.image_style_prompts import STYLE_PRESETS, COMMON_NEGATIVE_PROMPTS, format_image_generation_prompt
 
18
 
19
  # --- Initialize Services ---
20
  initialize_text_llms()
21
+ initialize_image_llms() # This will initialize based on HF_TOKEN for images
22
 
23
  # --- Get API Readiness Status ---
24
  GEMINI_TEXT_IS_READY = is_gemini_text_ready()
25
+ HF_TEXT_IS_READY = is_hf_text_ready()
26
+ HF_IMAGE_IS_READY = is_hf_image_api_ready() # Primary image status
 
 
27
 
28
  # --- Application Configuration (Models, Defaults) ---
29
  TEXT_MODELS = {}
30
  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
  if HF_TEXT_IS_READY:
34
  TEXT_MODELS["Mistral 7B (Narrate via HF)"] = {"id": "mistralai/Mistral-7B-Instruct-v0.2", "type": "hf_text"}
35
  TEXT_MODELS["Gemma 2B (Narrate via HF)"] = {"id": "google/gemma-2b-it", "type": "hf_text"}
 
37
  if TEXT_MODELS:
38
  if GEMINI_TEXT_IS_READY and "✨ Gemini 1.5 Flash (Narrate)" in TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = "✨ Gemini 1.5 Flash (Narrate)"
39
  elif HF_TEXT_IS_READY and "Mistral 7B (Narrate via HF)" in TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = "Mistral 7B (Narrate via HF)"
40
+ else: UI_DEFAULT_TEXT_MODEL_KEY = list(TEXT_MODELS.keys())[0]
41
  else:
42
  TEXT_MODELS["No Text Models Configured"] = {"id": "dummy_text_error", "type": "none"}
43
  UI_DEFAULT_TEXT_MODEL_KEY = "No Text Models Configured"
44
 
 
45
  IMAGE_PROVIDERS = {}
46
  UI_DEFAULT_IMAGE_PROVIDER_KEY = None
 
 
47
  if HF_IMAGE_IS_READY:
48
+ IMAGE_PROVIDERS["🎑 HF - Stable Diffusion XL Base"] = "hf_sdxl_base"
49
+ IMAGE_PROVIDERS["🎠 HF - OpenJourney (Midjourney-like)"] = "hf_openjourney"
50
+ IMAGE_PROVIDERS["🌌 HF - Stable Diffusion v1.5"] = "hf_sd_1_5" # Another option
51
+ UI_DEFAULT_IMAGE_PROVIDER_KEY = "🎑 HF - Stable Diffusion XL Base"
 
 
 
 
 
52
  else:
53
+ IMAGE_PROVIDERS["No Image Providers Configured (HF Token needed)"] = "none"
54
+ UI_DEFAULT_IMAGE_PROVIDER_KEY = "No Image Providers Configured (HF Token needed)"
55
 
56
 
57
  # --- Gradio UI Theme and CSS ---
 
75
  .output-section-header { font-size: 1.8em; font-weight: 600; color: #D0D0FF; margin-top: 15px; margin-bottom: 12px;}
76
  .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;}
77
  .gr-button { border-radius: 8px !important; font-weight: 500 !important; transition: all 0.2s ease-in-out !important;}
78
+ .gr-button-primary { padding-top: 10px !important; padding-bottom: 10px !important; }
79
  .gr-button-primary:hover { transform: scale(1.03) translateY(-1px) !important; box-shadow: 0 8px 16px rgba(127,0,255,0.3) !important; }
80
  .panel_image { border-radius: 12px !important; overflow: hidden; box-shadow: 0 6px 15px rgba(0,0,0,0.25) !important; background-color: #23233A;}
81
  .panel_image img { max-height: 600px !important; }
 
100
 
101
  # --- Helper: Placeholder Image Creation ---
102
  def create_placeholder_image(text="Processing...", size=(512, 512), color="#23233A", text_color="#E0E0FF"):
 
103
  img = Image.new('RGB', size, color=color); draw = ImageDraw.Draw(img)
104
  try: font_path = "arial.ttf" if os.path.exists("arial.ttf") else None
105
  except: font_path = None
 
109
  else: tw, th = draw.textsize(text, font=font)
110
  draw.text(((size[0]-tw)/2, (size[1]-th)/2), text, font=font, fill=text_color); return img
111
 
 
112
  # --- StoryVerse Weaver Orchestrator ---
113
  def add_scene_to_story_orchestrator(
114
  current_story_obj: Story, scene_prompt_text: str, image_style_dropdown: str, artist_style_text: str,
 
121
 
122
  log_accumulator = [f"**πŸš€ Scene {current_story_obj.current_scene_number + 1} - {time.strftime('%H:%M:%S')}**"]
123
 
 
124
  ret_story_state = current_story_obj
125
  ret_gallery = current_story_obj.get_all_scenes_for_gallery_display()
126
+ ret_latest_image = None
127
+ ret_latest_narrative_md_obj = gr.Markdown(value="## Processing...\nNarrative being woven...")
128
+ ret_status_bar_html_obj = gr.HTML(value="<p class='processing_text status_text'>Processing...</p>")
129
+ # ret_log_md will be built up
130
 
131
+ # Initial yield for UI updates (buttons disabled by .then() chain)
 
132
  yield {
133
+ output_status_bar: gr.HTML(value=f"<p class='processing_text status_text'>🌌 Weaving Scene {current_story_obj.current_scene_number + 1}...</p>"),
134
+ output_latest_scene_image: gr.Image(value=create_placeholder_image("🎨 Conjuring visuals...")),
135
+ output_latest_scene_narrative: gr.Markdown(value=" Musing narrative..."),
136
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator))
137
  }
138
 
 
152
  text_response = None
153
  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)
154
  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)
155
+ if text_response and text_response.success: narrative_text_generated = basic_text_cleanup(text_response.text); log_accumulator.append(f" Narrative: Success.")
156
+ elif text_response: narrative_text_generated = f"**Narrative Error ({text_model_key}):** {text_response.error}"; log_accumulator.append(f" Narrative: FAILED - {text_response.error}")
157
+ else: log_accumulator.append(f" Narrative: FAILED - No response object from {text_model_key}.")
158
+ 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.")
 
 
 
 
 
 
 
 
159
 
160
  ret_latest_narrative_str_content = f"## Scene Idea: {scene_prompt_text}\n\n{narrative_text_generated}"
161
+ ret_latest_narrative_md_obj = gr.Markdown(value=ret_latest_narrative_str_content) # Update the component object
162
  yield { output_latest_scene_narrative: ret_latest_narrative_md_obj,
163
  output_interaction_log_markdown: gr.Markdown(value="\n".join(log_accumulator)) }
164
 
 
166
  progress(0.5, desc="🎨 Conjuring visuals...")
167
  image_generated_pil = None
168
  image_generation_error_message = None
169
+ selected_image_provider_key_from_ui = image_provider_key # The key from the dropdown
170
+ selected_image_provider_type = IMAGE_PROVIDERS.get(selected_image_provider_key_from_ui) # The value (e.g., "hf_sdxl_base")
171
+
172
  image_content_prompt_for_gen = narrative_text_generated if narrative_text_generated and "Error" not in narrative_text_generated else scene_prompt_text
173
  quality_keyword = "ultra detailed, intricate, masterpiece, " if image_quality == "High Detail" else ("concept sketch, line art, " if image_quality == "Sketch Concept" else "")
174
  full_image_prompt = format_image_generation_prompt(quality_keyword + image_content_prompt_for_gen[:350], image_style_dropdown, artist_style_text)
175
+ log_accumulator.append(f" Image: Using provider key '{selected_image_provider_key_from_ui}' (maps to type '{selected_image_provider_type}'). Style: {image_style_dropdown}.")
176
 
177
  if selected_image_provider_type and selected_image_provider_type != "none":
178
+ if not HF_IMAGE_IS_READY and selected_image_provider_type.startswith("hf_"): # Check if HF image specifically is ready
179
+ image_generation_error_message = "**Image Error:** Hugging Face Image API not configured (check STORYVERSE_HF_TOKEN)."
180
+ log_accumulator.append(f" Image: FAILED - {image_generation_error_message}")
 
 
 
 
 
 
 
 
 
181
  else:
182
+ image_response = None
183
+ hf_model_id_to_call = None
184
+ if selected_image_provider_type == "hf_sdxl_base": hf_model_id_to_call = "stabilityai/stable-diffusion-xl-base-1.0"
185
+ elif selected_image_provider_type == "hf_openjourney": hf_model_id_to_call = "prompthero/openjourney"; img_width, img_height = 512, 512
186
+ elif selected_image_provider_type == "hf_sd_1_5": hf_model_id_to_call = "runwayml/stable-diffusion-v1-5"; img_width, img_height = 512, 512
187
+ # Add other specific HF model mappings here
188
+
189
+ if hf_model_id_to_call: # If it's an HF model type
190
+ image_response = generate_image_hf_model(
191
+ full_image_prompt, model_id=hf_model_id_to_call,
192
+ negative_prompt=negative_prompt_text or COMMON_NEGATIVE_PROMPTS,
193
+ width=img_width if 'img_width' in locals() else 768,
194
+ height=img_height if 'img_height' in locals() else 768
195
+ )
196
+ # Add elif for other providers like StabilityAI or DALL-E if you re-enable them
197
+ # elif selected_image_provider_type == "stability_ai": ...
198
+ else:
199
+ image_generation_error_message = f"**Image Error:** Provider type '{selected_image_provider_type}' not handled."
200
+ log_accumulator.append(f" Image: FAILED - {image_generation_error_message}")
201
+
202
+ if image_response and image_response.success:
203
+ image_generated_pil = image_response.image
204
+ log_accumulator.append(f" Image: Success from {image_response.provider} (Model: {image_response.model_id_used}).")
205
+ elif image_response:
206
+ image_generation_error_message = f"**Image Error ({image_response.provider} - {image_response.model_id_used}):** {image_response.error}"
207
+ log_accumulator.append(f" Image: FAILED - {image_response.error}")
208
+ elif not image_generation_error_message: # If no response and no specific error yet
209
+ image_generation_error_message = f"**Image Error:** No response from image service for {selected_image_provider_key_from_ui}."
210
+ log_accumulator.append(f" Image: FAILED - No response object.")
211
  else:
212
+ image_generation_error_message = "**Image Error:** No image provider selected or configured."
213
+ log_accumulator.append(f" Image: FAILED - Provider key '{selected_image_provider_key_from_ui}' not found or type is 'none'.")
214
 
215
  ret_latest_image = image_generated_pil if image_generated_pil else create_placeholder_image("Image Gen Failed", color="#401010")
216
  yield { output_latest_scene_image: gr.Image(value=ret_latest_image),
 
227
  narrative_text=narrative_text_generated if "**Narrative Error**" not in narrative_text_generated else "(Narrative gen failed)",
228
  image=image_generated_pil,
229
  image_style_prompt=f"{image_style_dropdown}{f', by {artist_style_text}' if artist_style_text and artist_style_text.strip() else ''}",
230
+ image_provider=selected_image_provider_key_from_ui, # Use the UI key for display
231
  error_message=final_scene_error
232
  )
233
  ret_story_state = current_story_obj
 
251
  log_accumulator.append(f"\n**UNEXPECTED RUNTIME ERROR:** {type(e).__name__} - {e}\n{traceback.format_exc()}")
252
  ret_status_bar_html_obj = gr.HTML(value=f"<p class='error_text status_text'>❌ UNEXPECTED ERROR: {type(e).__name__}. Check logs.</p>")
253
  ret_latest_narrative_md_obj = gr.Markdown(value=f"## Unexpected Error\n{type(e).__name__}: {e}\nSee log for details.")
254
+
255
+ # Final log update happens as part of the main return now
256
  current_total_time = time.time() - start_time
257
  log_accumulator.append(f" Cycle ended at {time.strftime('%H:%M:%S')}. Total time: {current_total_time:.2f}s")
258
  ret_log_md = gr.Markdown(value="\n".join(log_accumulator))
259
 
260
+ # This is the FINAL return. It must be a tuple matching the `outputs` list of engage_button.click()
261
  return (
262
+ ret_story_state,
263
+ ret_gallery,
264
+ ret_latest_image,
265
+ ret_latest_narrative_md_obj,
266
+ ret_status_bar_html_obj,
267
+ ret_log_md
268
  )
269
 
270
  def clear_story_state_ui_wrapper():
 
286
 
287
  # --- Gradio UI Definition ---
288
  with gr.Blocks(theme=omega_theme, css=omega_css, title="✨ StoryVerse Omega ✨") 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 = []; 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)
296
  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>")
297
  else:
298
  if text_llm_ok: status_text_list.append("<p style='color:#A7F3D0;'>βœ… Text Generation Service(s) Ready.</p>")
 
308
  scene_prompt_input = gr.Textbox(lines=7, label="Scene Vision (Description, Dialogue, Action):", placeholder="e.g., Amidst swirling cosmic dust...")
309
  with gr.Row(elem_classes=["compact-row"]):
310
  with gr.Column(scale=2):
311
+ 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)
312
  with gr.Column(scale=2):
313
  artist_style_input = gr.Textbox(label="Artistic Inspiration (Optional):", placeholder="e.g., Moebius...")
314
  negative_prompt_input = gr.Textbox(lines=2, label="Exclude from Image (Negative Prompt):", value=COMMON_NEGATIVE_PROMPTS)