mgbam commited on
Commit
1c5c923
·
verified ·
1 Parent(s): fe60278

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +204 -160
app.py CHANGED
@@ -2,59 +2,102 @@
2
  import gradio as gr
3
  import os
4
  import time
5
- from PIL import Image
6
 
7
  # --- Core Logic Imports ---
 
8
  from core.llm_services import initialize_text_llms, is_gemini_text_ready, is_hf_text_ready, generate_text_gemini, generate_text_hf
9
- from core.image_services import initialize_image_llms, STABILITY_API_CONFIGURED, OPENAI_DALLE_CONFIGURED, generate_image_stabilityai, generate_image_dalle # Add other providers if implemented
10
- from core.story_engine import Story, Scene # Manages the story
11
  from prompts.narrative_prompts import get_narrative_system_prompt, format_narrative_user_prompt
12
  from prompts.image_style_prompts import STYLE_PRESETS, COMMON_NEGATIVE_PROMPTS, format_image_generation_prompt
13
  from core.utils import basic_text_cleanup
14
 
15
- # --- Initialize Services ---
16
  initialize_text_llms()
17
  initialize_image_llms()
18
 
19
- # --- Available Model Configuration (Simplified for StoryVerse) ---
20
- # Text Models
 
 
 
 
 
21
  TEXT_MODELS = {}
22
- if is_gemini_text_ready():
23
- TEXT_MODELS["Gemini 1.5 Flash (Text)"] = {"id": "gemini-1.5-flash-latest", "type": "gemini"}
24
- TEXT_MODELS["Gemini 1.0 Pro (Text)"] = {"id": "gemini-1.0-pro-latest", "type": "gemini"}
25
- if is_hf_text_ready():
 
 
26
  TEXT_MODELS["Mistral 7B (HF Text)"] = {"id": "mistralai/Mistral-7B-Instruct-v0.2", "type": "hf_text"}
27
- DEFAULT_TEXT_MODEL_KEY = list(TEXT_MODELS.keys())[0] if TEXT_MODELS else "No Text Models Available"
 
 
 
 
 
 
 
 
 
28
 
29
- # Image Models (Providers)
30
  IMAGE_PROVIDERS = {}
31
- if STABILITY_API_CONFIGURED: IMAGE_PROVIDERS["Stability AI (Stable Diffusion XL)"] = "stability_ai"
32
- if OPENAI_DALLE_CONFIGURED: IMAGE_PROVIDERS["OpenAI DALL-E 3 (Simulated)"] = "dalle"
33
- # Add other HF image models if you implement image_services.generate_image_hf_model
34
- DEFAULT_IMAGE_PROVIDER_KEY = list(IMAGE_PROVIDERS.keys())[0] if IMAGE_PROVIDERS else "No Image Providers Available"
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
 
37
  # --- Gradio UI Theme and CSS ---
38
  story_theme = gr.themes.Soft(
39
  primary_hue=gr.themes.colors.purple,
40
  secondary_hue=gr.themes.colors.pink,
 
41
  font=[gr.themes.GoogleFont("Quicksand"), "ui-sans-serif", "system-ui", "sans-serif"]
42
  )
43
  custom_css = """
44
- .gradio-container { max-width: 1200px !important; margin: auto !important; }
45
- .panel_image img { object-fit: contain; width: 100%; height: 100%; max-height: 512px; }
46
- .gallery_output .thumbnail-item { height: 150px !important; width: 150px !important; }
 
 
47
  .gallery_output .thumbnail-item img { height: 100% !important; width: 100% !important; object-fit: cover !important; }
48
- .status_text { font-weight: bold; padding: 8px; text-align: center; border-radius: 5px; margin-top:10px;}
49
- .error_text { background-color: #ffebee; color: #c62828; }
50
- .success_text { background-color: #e8f5e9; color: #2e7d32; }
51
- .processing_text { background-color: #e3f2fd; color: #1565c0; }
52
- .compact-row .gr-form {gap: 8px !important;} /* Reduce gap in rows */
 
 
 
 
 
 
 
 
 
 
 
53
  """
54
 
55
  # --- StoryVerse Weaver Orchestrator ---
56
  def add_scene_to_story(
57
- current_story_obj: Story, # Comes from gr.State
58
  scene_prompt_text: str,
59
  image_style_dropdown: str,
60
  artist_style_text: str,
@@ -64,183 +107,178 @@ def add_scene_to_story(
64
  progress=gr.Progress(track_tqdm=True)
65
  ):
66
  if not scene_prompt_text.strip():
67
- return current_story_obj, None, "<p class='error_text status_text'>Scene prompt cannot be empty!</p>"
68
 
69
  progress(0, desc="Initializing new scene...")
70
- log_updates = ["Starting new scene generation..."]
 
71
 
72
  # --- 1. Generate Narrative Text ---
73
- progress(0.2, desc="Generating narrative...")
74
- narrative_text_generated = "Narrative generation failed."
75
  text_model_info = TEXT_MODELS.get(text_model_key)
76
 
77
- if text_model_info:
78
- system_p = get_narrative_system_prompt("default") # or "comic"
79
- # Could use last scene's narrative for context if desired
80
- # prev_narrative = current_story_obj.get_last_scene_narrative()
81
- user_p = format_narrative_user_prompt(scene_prompt_text) #, prev_narrative)
82
 
83
  text_response = None
 
84
  if text_model_info["type"] == "gemini":
85
- text_response = generate_text_gemini(user_p, model_id=text_model_info["id"], system_prompt=system_p, max_tokens=300)
86
  elif text_model_info["type"] == "hf_text":
87
- text_response = generate_text_hf(user_p, model_id=text_model_info["id"], system_prompt=system_p, max_tokens=300)
88
 
89
  if text_response and text_response.success:
90
  narrative_text_generated = basic_text_cleanup(text_response.text)
91
- log_updates.append(f"Narrative generated using {text_model_key}.")
92
  elif text_response:
93
- narrative_text_generated = f"Narrative Error: {text_response.error}"
94
- log_updates.append(f"Narrative generation FAILED with {text_model_key}: {text_response.error}")
95
  else:
96
- log_updates.append(f"Narrative generation FAILED with {text_model_key}: No response object.")
97
  else:
98
- narrative_text_generated = "Selected text model not available."
99
- log_updates.append("Narrative generation FAILED: Text model not found.")
100
 
101
 
102
  # --- 2. Generate Image ---
103
- progress(0.6, desc="Generating image...")
104
- image_generated = None
105
- image_error = None
106
- selected_image_provider = IMAGE_PROVIDERS.get(image_provider_key)
107
 
108
- # Use the generated narrative (or original prompt if narrative failed) for image prompt
109
- image_content_prompt = narrative_text_generated if narrative_text_generated and "Error" not in narrative_text_generated else scene_prompt_text
110
- full_image_prompt = format_image_generation_prompt(image_content_prompt[:300], image_style_dropdown, artist_style_text) # Limit prompt length for image gen
111
 
112
- if selected_image_provider:
113
- image_response = None
114
- if selected_image_provider == "stability_ai":
115
- image_response = generate_image_stabilityai(full_image_prompt, style_preset=None, negative_prompt=negative_prompt_text or COMMON_NEGATIVE_PROMPTS)
116
- elif selected_image_provider == "dalle":
117
- image_response = generate_image_dalle(full_image_prompt) # Uses default DALL-E settings from image_services
 
 
 
118
  # Add elif for HF image models if implemented
119
 
120
  if image_response and image_response.success:
121
- image_generated = image_response.image
122
- log_updates.append(f"Image generated using {image_provider_key}.")
123
  elif image_response:
124
- image_error = f"Image Error ({image_provider_key}): {image_response.error}"
125
- log_updates.append(f"Image generation FAILED with {image_provider_key}: {image_response.error}")
126
  else:
127
- image_error = f"Image generation failed: No response from {image_provider_key} service."
128
- log_updates.append(f"Image generation FAILED with {image_provider_key}: No response object.")
129
  else:
130
- image_error = "Selected image provider not available."
131
- log_updates.append("Image generation FAILED: Image provider not found.")
132
 
133
  # --- 3. Add Scene to Story Object ---
134
- if image_error and "Error" in narrative_text_generated: # Both failed
135
- current_story_obj.add_scene_with_error(scene_prompt_text, f"Narrative: {narrative_text_generated}. Image: {image_error}")
136
- else:
137
- current_story_obj.add_scene_from_elements(
138
- user_prompt=scene_prompt_text,
139
- narrative_text=narrative_text_generated,
140
- image=image_generated,
141
- image_style_prompt=f"{image_style_dropdown}{f', by {artist_style_text}' if artist_style_text else ''}",
142
- image_provider=image_provider_key
143
- )
 
 
 
 
 
 
 
144
 
145
  progress(1.0, desc="Scene complete!")
146
 
147
  # --- 4. Prepare Outputs for Gradio ---
148
- # Gallery expects list of (image_path_or_PIL, caption_string) tuples
149
- gallery_items = []
150
- for scene in current_story_obj.scenes:
151
- caption = f"S{scene.scene_number}: {scene.user_prompt[:40]}..."
152
- if scene.error_message:
153
- # Create a placeholder image for errors or display error text
154
- error_img = Image.new('RGB', (100,100), color='red') # Simple red square
155
- gallery_items.append((error_img, f"{caption}\nError: {scene.error_message[:100]}..."))
156
- else:
157
- gallery_items.append((scene.image if scene.image else Image.new('RGB', (100,100), color='grey'), caption)) # Grey if no image but no error
158
-
159
- # Display the latest scene's full details
160
- latest_scene_display = ""
161
- if current_story_obj.scenes:
162
- ls = current_story_obj.scenes[-1]
163
- latest_scene_display = f"## Scene {ls.scene_number}: {ls.user_prompt}\n\n"
164
- if ls.error_message:
165
- latest_scene_display += f"**Error:** {ls.error_message}\n"
166
- else:
167
- if ls.image:
168
- # Gradio Markdown can't directly display PIL.Image. We'll show it in the gallery.
169
- # For single image display, use gr.Image component.
170
- latest_scene_display += f"**Style:** {ls.image_style_prompt}\n\n"
171
- latest_scene_display += f"{ls.narrative_text}"
172
 
173
- # Determine status message
174
  status_message_html = ""
175
- if image_error or "Error" in narrative_text_generated:
176
- status_message_html = f"<p class='error_text status_text'>Scene added with errors. Narrative: {'OK' if 'Error' not in narrative_text_generated else 'Failed'}. Image: {'OK' if not image_error else 'Failed'}.</p>"
177
  else:
178
- status_message_html = "<p class='success_text status_text'>New scene added successfully!</p>"
179
 
180
- # For the single image display component, show the latest generated image
181
- latest_image_output = image_generated if image_generated else None # (or a placeholder if error)
182
-
183
- return current_story_obj, gallery_items, latest_image_output, latest_scene_display, status_message_html, "\n".join(log_updates)
184
 
185
 
186
- def clear_story_state():
187
  new_story = Story()
188
- return new_story, [], None, "Story Cleared. Ready for a new verse!", "<p class='status_text'>Story Cleared</p>", "Log Cleared."
 
 
 
 
189
 
190
  # --- Gradio UI Definition ---
191
- with gr.Blocks(theme=story_theme, css=custom_css) as story_weaver_demo:
192
  story_state = gr.State(Story()) # Manages the story object
193
 
194
- gr.Markdown("# ✨ StoryVerse Weaver ✨\nCreate multimodal stories with AI-generated narrative and images!")
 
195
 
196
- # API Status Check (Conceptual - real app might hide this or make it admin-only)
197
- with gr.Accordion("API & Model Status (Developer Info)", open=False):
198
- status_text = []
199
- if not GEMINI_API_READY and not HF_API_READY and not STABILITY_API_CONFIGURED and not OPENAI_DALLE_CONFIGURED:
200
- status_text.append("<p style='color:red;font-weight:bold;'>⚠️ CRITICAL: NO APIs CONFIGURED. App will be non-functional.</p>")
201
- else:
202
- if GEMINI_API_READY or HF_API_READY: status_text.append("<p style='color:green;'>✅ Text LLM(s) Ready.</p>")
203
- else: status_text.append("<p style='color:orange;'>⚠️ No Text LLMs Ready (Check STORYVERSE_GOOGLE_API_KEY/STORYVERSE_HF_TOKEN).</p>")
204
- if STABILITY_API_CONFIGURED or OPENAI_DALLE_CONFIGURED: status_text.append("<p style='color:green;'>✅ Image Generation Service(s) Ready.</p>")
205
- else: status_text.append("<p style='color:orange;'>⚠️ No Image Generation Services Ready (Check API Keys).</p>")
206
- gr.HTML("".join(status_text))
207
 
 
 
 
 
 
 
 
 
 
208
 
209
- with gr.Row():
210
  # --- CONTROL PANEL (Inputs) ---
211
- with gr.Column(scale=1):
212
- gr.Markdown("### 🎬 Scene Input")
213
- scene_prompt_input = gr.Textbox(lines=5, label="Describe your scene or story beat:", placeholder="e.g., A lone astronaut discovers a glowing alien artifact on a desolate moon.")
 
214
 
215
  with gr.Accordion("🎨 Visual Style (Optional)", open=True):
216
- image_style_input = gr.Dropdown(choices=["Default"] + list(STYLE_PRESETS.keys()), value="Default", label="Image Style Preset")
217
- artist_style_input = gr.Textbox(label="Inspired by Artist (Optional):", placeholder="e.g., Van Gogh, Hayao Miyazaki, HR Giger")
218
- negative_prompt_input = gr.Textbox(lines=2, label="Negative Prompt (Optional):", placeholder="e.g., blurry, text, watermark, poorly drawn", value=COMMON_NEGATIVE_PROMPTS)
 
219
 
220
  with gr.Accordion("⚙️ AI Configuration (Advanced)", open=False):
221
- text_model_dropdown = gr.Dropdown(choices=list(TEXT_MODELS.keys()), value=DEFAULT_TEXT_MODEL_KEY, label="Text Generation Model")
222
- image_provider_dropdown = gr.Dropdown(choices=list(IMAGE_PROVIDERS.keys()), value=DEFAULT_IMAGE_PROVIDER_KEY, label="Image Generation Provider")
223
- # Could add sliders for temperature, tokens etc. here later
224
 
225
- with gr.Row(elem_classes=["compact-row"]):
226
- add_scene_button = gr.Button("➕ Weave Next Scene", variant="primary")
227
- clear_story_button = gr.Button("🗑️ Clear Story")
228
 
229
- status_bar_output = gr.HTML(value="<p class='status_text'>Ready to weave...</p>")
230
 
231
  # --- STORY DISPLAY (Outputs) ---
232
- with gr.Column(scale=2):
233
- gr.Markdown("### 📖 Your StoryVerse So Far")
234
 
235
  with gr.Tabs():
236
- with gr.TabItem("🖼️ Latest Scene View"):
237
- latest_scene_image_output = gr.Image(label="Latest Scene Image", type="pil", interactive=False, show_download_button=True, elem_classes=["panel_image"])
238
- latest_scene_narrative_output = gr.Markdown(label="Latest Scene Narrative")
 
239
 
240
- with gr.TabItem(" галерея | Story Scroll"): # Gallery in Russian, for fun :)
241
- story_gallery_output = gr.Gallery(label="Story Scroll", show_label=False, columns=[3], object_fit="contain", height="auto", elem_classes=["gallery_output"])
242
 
243
- with gr.TabItem("📜 Interaction Log"):
244
  log_output_markdown = gr.Markdown("Log will appear here...")
245
 
246
  # --- Event Handlers ---
@@ -251,14 +289,14 @@ with gr.Blocks(theme=story_theme, css=custom_css) as story_weaver_demo:
251
  image_style_input, artist_style_input, negative_prompt_input,
252
  text_model_dropdown, image_provider_dropdown
253
  ],
254
- outputs=[ # Order must match the return order of add_scene_to_story
255
  story_state, story_gallery_output,
256
  latest_scene_image_output, latest_scene_narrative_output,
257
  status_bar_output, log_output_markdown
258
  ]
259
  )
260
  clear_story_button.click(
261
- fn=clear_story_state,
262
  inputs=[],
263
  outputs=[
264
  story_state, story_gallery_output,
@@ -267,33 +305,39 @@ with gr.Blocks(theme=story_theme, css=custom_css) as story_weaver_demo:
267
  ]
268
  )
269
 
270
- # Example Prompts for User
271
  gr.Examples(
272
  examples=[
273
- ["A knight faces a dragon in a fiery volcano.", "Fantasy Art", "Frank Frazetta", "blurry, low quality"],
274
- ["A futuristic detective investigates a crime in a neon-lit alley.", "Cyberpunk", "Syd Mead", "cartoon, painting"],
275
- ["Two children discover a hidden portal in an old oak tree.", "Studio Ghibli Inspired", "", "dark, scary"],
276
- ["A single red rose blooming in a post-apocalyptic wasteland.", "Photorealistic", "Ansel Adams", "oversaturated, vibrant"],
277
  ],
278
  inputs=[scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input],
279
- label="✨ Example Scene Ideas & Styles ✨"
 
280
  )
 
281
 
282
  # --- Entry Point ---
283
  if __name__ == "__main__":
284
  print("="*80)
285
  print("✨ StoryVerse Weaver™ - Multimodal Story Creator - Launching... ✨")
286
- print(f" Text LLM Ready (Gemini): {is_gemini_text_ready()}")
287
- print(f" Text LLM Ready (HF): {is_hf_text_ready()}")
288
- print(f" Image Provider Ready (Stability AI): {STABILITY_API_CONFIGURED}")
289
- print(f" Image Provider Ready (DALL-E): {OPENAI_DALLE_CONFIGURED}")
290
- if not (is_gemini_text_ready() or is_hf_text_ready()) or not (STABILITY_API_CONFIGURED or OPENAI_DALLE_CONFIGURED):
291
- print(" 🔴 WARNING: Not all required API services are configured. Functionality will be limited or fail.")
292
- print(" Please set: STORYVERSE_GOOGLE_API_KEY (for Gemini text), and/or STORYVERSE_HF_TOKEN (for HF text),")
293
- print(" AND STORYVERSE_STABILITY_API_KEY (for Stability AI images) or STORYVERSE_OPENAI_API_KEY (for DALL-E images) in your environment/secrets.")
294
- print(f" Default Text Model: {DEFAULT_TEXT_MODEL_KEY}")
295
- print(f" Default Image Provider: {DEFAULT_IMAGE_PROVIDER_KEY}")
 
 
 
 
296
  print(f" Available Text Models: {list(TEXT_MODELS.keys())}")
297
  print(f" Available Image Providers: {list(IMAGE_PROVIDERS.keys())}")
298
  print("="*80)
 
299
  story_weaver_demo.launch(debug=True, server_name="0.0.0.0")
 
2
  import gradio as gr
3
  import os
4
  import time
5
+ from PIL import Image # Ensure Pillow is in requirements.txt
6
 
7
  # --- Core Logic Imports ---
8
+ # Import initialization functions and status getters/flags
9
  from core.llm_services import initialize_text_llms, is_gemini_text_ready, is_hf_text_ready, generate_text_gemini, generate_text_hf
10
+ from core.image_services import initialize_image_llms, STABILITY_API_CONFIGURED, OPENAI_DALLE_CONFIGURED, generate_image_stabilityai, generate_image_dalle
11
+ from core.story_engine import Story, Scene # Manages the story object
12
  from prompts.narrative_prompts import get_narrative_system_prompt, format_narrative_user_prompt
13
  from prompts.image_style_prompts import STYLE_PRESETS, COMMON_NEGATIVE_PROMPTS, format_image_generation_prompt
14
  from core.utils import basic_text_cleanup
15
 
16
+ # --- Initialize All External Services ONCE at App Startup ---
17
  initialize_text_llms()
18
  initialize_image_llms()
19
 
20
+ # --- Get API Readiness Status AFTER initialization for use in this module ---
21
+ GEMINI_TEXT_IS_READY = is_gemini_text_ready()
22
+ HF_TEXT_IS_READY = is_hf_text_ready()
23
+ STABILITY_API_IS_READY = STABILITY_API_CONFIGURED # Directly use the flag from image_services
24
+ OPENAI_DALLE_IS_READY = OPENAI_DALLE_CONFIGURED # Directly use the flag from image_services
25
+
26
+ # --- Application Configuration (Models, Defaults) ---
27
  TEXT_MODELS = {}
28
+ UI_DEFAULT_TEXT_MODEL_KEY = None
29
+
30
+ if GEMINI_TEXT_IS_READY:
31
+ TEXT_MODELS["✨ Gemini 1.5 Flash (Text)"] = {"id": "gemini-1.5-flash-latest", "type": "gemini"}
32
+ TEXT_MODELS["Legacy Gemini 1.0 Pro (Text)"] = {"id": "gemini-1.0-pro-latest", "type": "gemini"}
33
+ if HF_TEXT_IS_READY:
34
  TEXT_MODELS["Mistral 7B (HF Text)"] = {"id": "mistralai/Mistral-7B-Instruct-v0.2", "type": "hf_text"}
35
+ TEXT_MODELS["Gemma 2B (HF Text)"] = {"id": "google/gemma-2b-it", "type": "hf_text"}
36
+
37
+ if TEXT_MODELS:
38
+ if "✨ Gemini 1.5 Flash (Text)" in TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = "✨ Gemini 1.5 Flash (Text)"
39
+ elif "Mistral 7B (HF Text)" in TEXT_MODELS: UI_DEFAULT_TEXT_MODEL_KEY = "Mistral 7B (HF Text)"
40
+ else: UI_DEFAULT_TEXT_MODEL_KEY = list(TEXT_MODELS.keys())[0]
41
+ else:
42
+ TEXT_MODELS["No Text Models Available"] = {"id": "dummy_text_error", "type": "none"}
43
+ UI_DEFAULT_TEXT_MODEL_KEY = "No Text Models Available"
44
+
45
 
 
46
  IMAGE_PROVIDERS = {}
47
+ UI_DEFAULT_IMAGE_PROVIDER_KEY = None
48
+
49
+ if STABILITY_API_IS_READY:
50
+ IMAGE_PROVIDERS["🎨 Stability AI (Stable Diffusion XL)"] = "stability_ai"
51
+ if OPENAI_DALLE_IS_READY:
52
+ IMAGE_PROVIDERS["🖼️ OpenAI DALL-E 3 (Simulated)"] = "dalle"
53
+ # Add other HF image models if you implement image_services.generate_image_hf_model, e.g.:
54
+ # if is_hf_text_ready(): # Re-use HF token if image model uses it
55
+ # IMAGE_PROVIDERS["🎡 HF Diffusers Model (Simulated)"] = "hf_image_model"
56
+
57
+ if IMAGE_PROVIDERS:
58
+ if "🎨 Stability AI (Stable Diffusion XL)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "🎨 Stability AI (Stable Diffusion XL)"
59
+ elif "🖼️ OpenAI DALL-E 3 (Simulated)" in IMAGE_PROVIDERS: UI_DEFAULT_IMAGE_PROVIDER_KEY = "🖼️ OpenAI DALL-E 3 (Simulated)"
60
+ else: UI_DEFAULT_IMAGE_PROVIDER_KEY = list(IMAGE_PROVIDERS.keys())[0]
61
+ else:
62
+ IMAGE_PROVIDERS["No Image Providers Available"] = "none"
63
+ UI_DEFAULT_IMAGE_PROVIDER_KEY = "No Image Providers Available"
64
 
65
 
66
  # --- Gradio UI Theme and CSS ---
67
  story_theme = gr.themes.Soft(
68
  primary_hue=gr.themes.colors.purple,
69
  secondary_hue=gr.themes.colors.pink,
70
+ neutral_hue=gr.themes.colors.slate,
71
  font=[gr.themes.GoogleFont("Quicksand"), "ui-sans-serif", "system-ui", "sans-serif"]
72
  )
73
  custom_css = """
74
+ body { font-family: 'Quicksand', sans-serif; background-color: #f0f2f5; }
75
+ .gradio-container { max-width: 1280px !important; margin: auto !important; background-color: #ffffff; border-radius: 15px; box-shadow: 0 8px 24px rgba(0,0,0,0.1); padding: 20px !important;}
76
+ .panel_image img { object-fit: contain; width: 100%; max-height: 512px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
77
+ .gallery_output { background-color: #f8f9fa !important; border-radius: 8px; padding: 10px; }
78
+ .gallery_output .thumbnail-item { height: 160px !important; width: 160px !important; border-radius: 6px; overflow: hidden; box-shadow: 0 2px 6px rgba(0,0,0,0.08); margin: 5px !important;}
79
  .gallery_output .thumbnail-item img { height: 100% !important; width: 100% !important; object-fit: cover !important; }
80
+ .status_text { font-weight: 500; padding: 10px 15px; text-align: center; border-radius: 6px; margin-top:10px; transition: all 0.3s ease;}
81
+ .error_text { background-color: #ffcdd2; color: #b71c1c; border: 1px solid #ef9a9a;} /* Red family */
82
+ .success_text { background-color: #c8e6c9; color: #1b5e20; border: 1px solid #a5d6a7;} /* Green family */
83
+ .processing_text { background-color: #bbdefb; color: #0d47a1; border: 1px solid #90caf9;} /* Blue family */
84
+ .compact-row .gr-form {gap: 8px !important;}
85
+ .gr-button-primary {
86
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; /* Purple gradient */
87
+ color: white !important; border: none !important; box-shadow: 0 4px 8px rgba(0,0,0,0.15) !important;
88
+ font-weight: 600 !important; transition: all 0.2s ease-in-out !important; padding: 10px 20px !important; border-radius: 8px !important;
89
+ }
90
+ .gr-button-primary:hover { transform: translateY(-2px) !important; box-shadow: 0 6px 12px rgba(0,0,0,0.2) !important; }
91
+ .gr-button-secondary { border-radius: 8px !important; }
92
+ .gr-markdown h1, .gr-markdown h2, .gr-markdown h3 { color: #4A00E0; } /* Purple headers */
93
+ .accordion-section .gr-markdown { padding-top: 5px; padding-bottom: 5px; }
94
+ .output-tabs .gr-tabitem {min-height: 450px;}
95
+ .important-note { background-color: #fff3cd; border-left: 5px solid #ffeeba; padding: 10px; margin-bottom:15px; color: #856404; border-radius: 4px;}
96
  """
97
 
98
  # --- StoryVerse Weaver Orchestrator ---
99
  def add_scene_to_story(
100
+ current_story_obj: Story,
101
  scene_prompt_text: str,
102
  image_style_dropdown: str,
103
  artist_style_text: str,
 
107
  progress=gr.Progress(track_tqdm=True)
108
  ):
109
  if not scene_prompt_text.strip():
110
+ return current_story_obj, [], None, "## Error\nScene prompt cannot be empty!", "<p class='error_text status_text'>Scene prompt cannot be empty!</p>", "Log: Scene prompt empty."
111
 
112
  progress(0, desc="Initializing new scene...")
113
+ log_accumulator = [f"**New Scene Generation - {time.strftime('%H:%M:%S')}**"]
114
+ if not current_story_obj: current_story_obj = Story() # Safety net
115
 
116
  # --- 1. Generate Narrative Text ---
117
+ progress(0.15, desc="✍️ Generating narrative...")
118
+ narrative_text_generated = f"Narrative generation failed for '{scene_prompt_text[:30]}...'."
119
  text_model_info = TEXT_MODELS.get(text_model_key)
120
 
121
+ if text_model_info and text_model_info["type"] != "none":
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
 
126
  text_response = None
127
+ log_accumulator.append(f" Narrative: Using {text_model_key} ({text_model_info['id']})")
128
  if text_model_info["type"] == "gemini":
129
+ text_response = generate_text_gemini(user_p, model_id=text_model_info["id"], system_prompt=system_p, max_tokens=350)
130
  elif text_model_info["type"] == "hf_text":
131
+ text_response = generate_text_hf(user_p, model_id=text_model_info["id"], system_prompt=system_p, max_tokens=350)
132
 
133
  if text_response and text_response.success:
134
  narrative_text_generated = basic_text_cleanup(text_response.text)
135
+ log_accumulator.append(f" Narrative: Success. (Snippet: {narrative_text_generated[:50]}...)")
136
  elif text_response:
137
+ narrative_text_generated = f"**Narrative Error ({text_model_key}):** {text_response.error}"
138
+ log_accumulator.append(f" Narrative: FAILED - {text_response.error}")
139
  else:
140
+ log_accumulator.append(f" Narrative: FAILED - No response object from {text_model_key}.")
141
  else:
142
+ narrative_text_generated = "**Narrative Error:** Selected text model not available or misconfigured."
143
+ log_accumulator.append(f" Narrative: FAILED - Model '{text_model_key}' not available.")
144
 
145
 
146
  # --- 2. Generate Image ---
147
+ progress(0.55, desc="🎨 Generating image...")
148
+ image_generated_pil = None
149
+ image_generation_error_message = None
150
+ selected_image_provider_type = IMAGE_PROVIDERS.get(image_provider_key)
151
 
152
+ image_content_prompt_for_gen = narrative_text_generated if narrative_text_generated and "Error" not in narrative_text_generated else scene_prompt_text
153
+ full_image_prompt = format_image_generation_prompt(image_content_prompt_for_gen[:350], image_style_dropdown, artist_style_text) # Limit length
 
154
 
155
+ log_accumulator.append(f" Image: Using {image_provider_key}. Style: {image_style_dropdown}. Artist: {artist_style_text or 'N/A'}.")
156
+ log_accumulator.append(f" Image Prompt (Base): {image_content_prompt_for_gen[:70]}...")
157
+
158
+ if selected_image_provider_type and selected_image_provider_type != "none":
159
+ image_response = None # type: ImageGenResponse
160
+ if selected_image_provider_type == "stability_ai":
161
+ image_response = generate_image_stabilityai(full_image_prompt, negative_prompt=negative_prompt_text or COMMON_NEGATIVE_PROMPTS)
162
+ elif selected_image_provider_type == "dalle":
163
+ image_response = generate_image_dalle(full_image_prompt)
164
  # Add elif for HF image models if implemented
165
 
166
  if image_response and image_response.success:
167
+ image_generated_pil = image_response.image
168
+ log_accumulator.append(f" Image: Success from {image_response.provider}.")
169
  elif image_response:
170
+ image_generation_error_message = f"**Image Error ({image_response.provider}):** {image_response.error}"
171
+ log_accumulator.append(f" Image: FAILED - {image_response.error}")
172
  else:
173
+ image_generation_error_message = f"**Image Error:** No response object from {image_provider_key} service."
174
+ log_accumulator.append(f" Image: FAILED - No response object from {image_provider_key}.")
175
  else:
176
+ image_generation_error_message = "**Image Error:** Selected image provider not available or misconfigured."
177
+ log_accumulator.append(f" Image: FAILED - Provider '{image_provider_key}' not available.")
178
 
179
  # --- 3. Add Scene to Story Object ---
180
+ final_scene_error = None
181
+ if image_generation_error_message and "**Narrative Error**" in narrative_text_generated :
182
+ final_scene_error = f"{narrative_text_generated}\n{image_generation_error_message}"
183
+ elif "**Narrative Error**" in narrative_text_generated:
184
+ final_scene_error = narrative_text_generated
185
+ elif image_generation_error_message:
186
+ final_scene_error = image_generation_error_message
187
+ # Keep generated narrative even if image fails
188
+
189
+ current_story_obj.add_scene_from_elements(
190
+ user_prompt=scene_prompt_text,
191
+ narrative_text=narrative_text_generated if "**Narrative Error**" not in narrative_text_generated else "(Narrative generation failed, see error above/below)",
192
+ image=image_generated_pil,
193
+ image_style_prompt=f"{image_style_dropdown}{f', by {artist_style_text}' if artist_style_text and artist_style_text.strip() else ''}",
194
+ image_provider=image_provider_key if selected_image_provider_type != "none" else "N/A",
195
+ error_message=final_scene_error
196
+ )
197
 
198
  progress(1.0, desc="Scene complete!")
199
 
200
  # --- 4. Prepare Outputs for Gradio ---
201
+ gallery_items_tuples = current_story_obj.get_all_scenes_for_gallery_display()
202
+ latest_img_for_display, latest_narr_for_display = current_story_obj.get_latest_scene_details_for_display()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
 
204
  status_message_html = ""
205
+ if final_scene_error:
206
+ status_message_html = f"<p class='error_text status_text'>Scene added with errors. Check details.</p>"
207
  else:
208
+ status_message_html = "<p class='success_text status_text'>✨ New scene woven into your StoryVerse! ✨</p>"
209
 
210
+ return current_story_obj, gallery_items_tuples, latest_img_for_display, latest_narr_for_display, status_message_html, "\n".join(log_accumulator)
 
 
 
211
 
212
 
213
+ def clear_story_state_ui():
214
  new_story = Story()
215
+ # Create a placeholder for the gallery when cleared
216
+ placeholder_img = Image.new('RGB', (150,150), color='lightgrey')
217
+ cleared_gallery = [(placeholder_img, "Your StoryVerse is empty. Weave a new scene!")]
218
+ return new_story, cleared_gallery, None, "## Story Cleared\nReady for a new verse!", "<p class='status_text'>Story Cleared. Let's begin anew!</p>", "Log Cleared."
219
+
220
 
221
  # --- Gradio UI Definition ---
222
+ with gr.Blocks(theme=story_theme, css=custom_css, title="✨ StoryVerse Weaver ✨") as story_weaver_demo:
223
  story_state = gr.State(Story()) # Manages the story object
224
 
225
+ gr.Markdown("# ✨ StoryVerse Weaver ✨\n### Weave Multimodal Stories with AI-Generated Narrative and Images!")
226
+ gr.HTML("<div class='important-note'>Provide a scene idea and style, then click 'Weave Next Scene'. API keys for text (Gemini or HF) and image generation (Stability AI or DALL-E) must be set in Space Secrets as `STORYVERSE_...` variables.</div>")
227
 
228
+ # API Status (More user-friendly)
229
+ with gr.Accordion("🔧 AI Services Status", open=False):
230
+ status_text_list = []
231
+ text_llm_ok = GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY
232
+ image_gen_ok = STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY
 
 
 
 
 
 
233
 
234
+ if not text_llm_ok and not image_gen_ok:
235
+ status_text_list.append("<p style='color:red;font-weight:bold;'>⚠️ CRITICAL: NO AI SERVICES CONFIGURED. App will not function. Please set API keys in Space Secrets.</p>")
236
+ else:
237
+ if text_llm_ok: status_text_list.append("<p style='color:green;'>✅ Text Generation Service(s) Ready.</p>")
238
+ else: status_text_list.append("<p style='color:orange;'>⚠️ Text Generation Service(s) NOT Ready (Check STORYVERSE_GOOGLE_API_KEY / STORYVERSE_HF_TOKEN).</p>")
239
+
240
+ if image_gen_ok: status_text_list.append("<p style='color:green;'>✅ Image Generation Service(s) Ready.</p>")
241
+ else: status_text_list.append("<p style='color:orange;'>⚠️ Image Generation Service(s) NOT Ready (Check STORYVERSE_STABILITY_API_KEY / STORYVERSE_OPENAI_API_KEY).</p>")
242
+ gr.HTML("".join(status_text_list))
243
 
244
+ with gr.Row(equal_height=False):
245
  # --- CONTROL PANEL (Inputs) ---
246
+ with gr.Column(scale=2, min_width=380): # Adjusted scale
247
+ gr.Markdown("### 🎬 **Input Your Scene Idea**")
248
+ with gr.Group():
249
+ scene_prompt_input = gr.Textbox(lines=6, label="Scene Description / Story Beat:", placeholder="e.g., A lone astronaut discovers a glowing alien artifact on a desolate, red moon, casting long shadows.")
250
 
251
  with gr.Accordion("🎨 Visual Style (Optional)", open=True):
252
+ with gr.Group():
253
+ image_style_input = gr.Dropdown(choices=["Default (Cinematic)"] + list(STYLE_PRESETS.keys()), value="Default (Cinematic)", label="Image Style Preset")
254
+ artist_style_input = gr.Textbox(label="Artistic Inspiration (Optional):", placeholder="e.g., inspired by Van Gogh, Studio Ghibli, Syd Mead")
255
+ negative_prompt_input = gr.Textbox(lines=2, label="Exclude from Image (Negative Prompt):", placeholder="e.g., blurry, text, watermark, poorly drawn hands", value=COMMON_NEGATIVE_PROMPTS)
256
 
257
  with gr.Accordion("⚙️ AI Configuration (Advanced)", open=False):
258
+ with gr.Group():
259
+ text_model_dropdown = gr.Dropdown(choices=list(TEXT_MODELS.keys()), value=UI_DEFAULT_TEXT_MODEL_KEY, label="Text Generation Model")
260
+ image_provider_dropdown = gr.Dropdown(choices=list(IMAGE_PROVIDERS.keys()), value=UI_DEFAULT_IMAGE_PROVIDER_KEY, label="Image Generation Provider")
261
 
262
+ with gr.Row(elem_classes=["compact-row"], equal_height=True):
263
+ add_scene_button = gr.Button("➕ Weave Next Scene", variant="primary", scale=2)
264
+ clear_story_button = gr.Button("🗑️ New Story", variant="secondary", scale=1)
265
 
266
+ status_bar_output = gr.HTML(value="<p class='processing_text status_text'>Ready to weave your first scene!</p>")
267
 
268
  # --- STORY DISPLAY (Outputs) ---
269
+ with gr.Column(scale=3, min_width=600): # Adjusted scale
270
+ gr.Markdown("### 📖 **Your StoryVerse Unfolds...**")
271
 
272
  with gr.Tabs():
273
+ with gr.TabItem("🖼️ Latest Scene", elem_id="latest_scene_tab"):
274
+ with gr.Row():
275
+ latest_scene_image_output = gr.Image(label="Latest Scene Image", type="pil", interactive=False, show_download_button=True, height=400, elem_classes=["panel_image"]) # Fixed height
276
+ latest_scene_narrative_output = gr.Markdown(label="Latest Scene Narrative") # Markdown can render images if path is given, but here it's for text
277
 
278
+ with gr.TabItem("📜 Story Scroll (All Scenes)", elem_id="story_scroll_tab"):
279
+ story_gallery_output = gr.Gallery(label="Story Scroll", show_label=False, columns=3, object_fit="cover", height=600, preview=True, elem_classes=["gallery_output"]) # Preview on click
280
 
281
+ with gr.TabItem("📝 Interaction Log", elem_id="log_tab"):
282
  log_output_markdown = gr.Markdown("Log will appear here...")
283
 
284
  # --- Event Handlers ---
 
289
  image_style_input, artist_style_input, negative_prompt_input,
290
  text_model_dropdown, image_provider_dropdown
291
  ],
292
+ outputs=[
293
  story_state, story_gallery_output,
294
  latest_scene_image_output, latest_scene_narrative_output,
295
  status_bar_output, log_output_markdown
296
  ]
297
  )
298
  clear_story_button.click(
299
+ fn=clear_story_state_ui, # Use the UI specific clear function
300
  inputs=[],
301
  outputs=[
302
  story_state, story_gallery_output,
 
305
  ]
306
  )
307
 
 
308
  gr.Examples(
309
  examples=[
310
+ ["A knight in shining armor bravely faces a colossal, fire-breathing dragon in front of a crumbling volcano fortress.", "Fantasy Art", "Frank Frazetta", "blurry, low quality, cartoon"],
311
+ ["In a rain-slicked, neon-drenched cyberpunk alley, a lone detective in a trench coat examines a mysterious datachip.", "Cyberpunk", "Syd Mead", "bright daytime, nature, animals"],
312
+ ["Two curious children stumble upon a glowing, ancient portal hidden within the roots of a giant, moss-covered oak tree in an enchanted forest.", "Studio Ghibli Inspired", "Hayao Miyazaki", "dark, scary, urban, modern"],
313
+ ["A single, perfect red rose defiantly blooms amidst the metallic ruins of a desolate, post-apocalyptic cityscape under a grey sky.", "Photorealistic", "Ansel Adams", "oversaturated colors, people, vibrant"],
314
  ],
315
  inputs=[scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input],
316
+ label="✨ Example Scene Ideas & Styles ✨",
317
+ # outputs=[scene_prompt_input, image_style_input, artist_style_input, negative_prompt_input] # To fill inputs
318
  )
319
+ gr.HTML("<p style='text-align:center; font-size:0.9em; color:grey; margin-top:20px;'>StoryVerse Weaver™ - Weaving Worlds with Words and Pixels</p>")
320
 
321
  # --- Entry Point ---
322
  if __name__ == "__main__":
323
  print("="*80)
324
  print("✨ StoryVerse Weaver™ - Multimodal Story Creator - Launching... ✨")
325
+ print(f" Text LLM Ready (Gemini): {GEMINI_TEXT_IS_READY}") # Using corrected var names
326
+ print(f" Text LLM Ready (HF): {HF_TEXT_IS_READY}")
327
+ print(f" Image Provider Ready (Stability AI): {STABILITY_API_IS_READY}")
328
+ print(f" Image Provider Ready (DALL-E): {OPENAI_DALLE_IS_READY}")
329
+
330
+ if not (GEMINI_TEXT_IS_READY or HF_TEXT_IS_READY) or \
331
+ not (STABILITY_API_IS_READY or OPENAI_DALLE_IS_READY):
332
+ print(" 🔴 WARNING: Not all required AI services are configured. Functionality will be limited or fail.")
333
+ print(" Please set environment variables/secrets for:")
334
+ print(" - Text: STORYVERSE_GOOGLE_API_KEY (for Gemini) and/or STORYVERSE_HF_TOKEN (for Hugging Face models)")
335
+ print(" - Image: STORYVERSE_STABILITY_API_KEY (for Stability AI) and/or STORYVERSE_OPENAI_API_KEY (for DALL-E)")
336
+
337
+ print(f" Default Text Model: {UI_DEFAULT_TEXT_MODEL_KEY}")
338
+ print(f" Default Image Provider: {UI_DEFAULT_IMAGE_PROVIDER_KEY}")
339
  print(f" Available Text Models: {list(TEXT_MODELS.keys())}")
340
  print(f" Available Image Providers: {list(IMAGE_PROVIDERS.keys())}")
341
  print("="*80)
342
+
343
  story_weaver_demo.launch(debug=True, server_name="0.0.0.0")