agharsallah
commited on
Commit
Β·
c323310
1
Parent(s):
69aaf62
adding Progress bar on chapter creation
Browse files- app.py +57 -2
- controllers/app_controller.py +85 -7
- services/streaming_chapter_processor.py +85 -5
- ui/components.py +26 -6
- ui/events.py +25 -1
app.py
CHANGED
@@ -8,6 +8,7 @@ from dotenv import load_dotenv
|
|
8 |
from pathlib import Path
|
9 |
from ui.styles import main_css
|
10 |
from ui.components import (
|
|
|
11 |
create_header,
|
12 |
create_instructions_accordion,
|
13 |
create_story_tab_header,
|
@@ -108,18 +109,71 @@ def create_app():
|
|
108 |
chapters_state = gr.State(value=None)
|
109 |
|
110 |
# Chapter processing loading animation
|
111 |
-
create_chapter_loading_container()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
|
113 |
@gr.render(inputs=[chapters_state])
|
114 |
def render_chapters(chapters_data):
|
115 |
if chapters_data is None:
|
116 |
return create_empty_chapters_placeholder()
|
117 |
|
118 |
-
# Check if chapters_data is a string indicating an error
|
119 |
if isinstance(chapters_data, str) and chapters_data.startswith(
|
120 |
"Error:"
|
121 |
):
|
122 |
return create_chapter_error_display(chapters_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
# Display story title with magical styling
|
125 |
create_story_title_display(
|
@@ -192,6 +246,7 @@ def create_app():
|
|
192 |
story_title=story_title,
|
193 |
story_text=story_text,
|
194 |
chapters_state=chapters_state,
|
|
|
195 |
)
|
196 |
|
197 |
return demo
|
|
|
8 |
from pathlib import Path
|
9 |
from ui.styles import main_css
|
10 |
from ui.components import (
|
11 |
+
create_chapter_loading_placeholder,
|
12 |
create_header,
|
13 |
create_instructions_accordion,
|
14 |
create_story_tab_header,
|
|
|
109 |
chapters_state = gr.State(value=None)
|
110 |
|
111 |
# Chapter processing loading animation
|
112 |
+
chapter_loading_container = create_chapter_loading_container()
|
113 |
+
|
114 |
+
# Function to show the chapter loading container when processing starts
|
115 |
+
def show_chapter_progress_container():
|
116 |
+
return gr.update(visible=True)
|
117 |
+
|
118 |
+
# Connect process_chapters_button to show the loading container
|
119 |
+
process_chapters_button.click(
|
120 |
+
show_chapter_progress_container,
|
121 |
+
inputs=[],
|
122 |
+
outputs=[chapter_loading_container],
|
123 |
+
)
|
124 |
|
125 |
@gr.render(inputs=[chapters_state])
|
126 |
def render_chapters(chapters_data):
|
127 |
if chapters_data is None:
|
128 |
return create_empty_chapters_placeholder()
|
129 |
|
130 |
+
# Check if chapters_data is a string indicating an error or dict with error key
|
131 |
if isinstance(chapters_data, str) and chapters_data.startswith(
|
132 |
"Error:"
|
133 |
):
|
134 |
return create_chapter_error_display(chapters_data)
|
135 |
+
elif isinstance(chapters_data, dict) and "error" in chapters_data:
|
136 |
+
return create_chapter_error_display(chapters_data["error"])
|
137 |
+
|
138 |
+
# Check if we're still processing
|
139 |
+
if isinstance(chapters_data, dict) and chapters_data.get(
|
140 |
+
"processing", False
|
141 |
+
):
|
142 |
+
# If chapters exist but we're still processing, show them with loading indicators
|
143 |
+
chapters = chapters_data.get("chapters", [])
|
144 |
+
if chapters:
|
145 |
+
# Display story title with magical styling
|
146 |
+
create_story_title_display(
|
147 |
+
chapters_data.get("title", "Story Chapters")
|
148 |
+
)
|
149 |
+
|
150 |
+
# Chapter navigation
|
151 |
+
create_chapter_navigation()
|
152 |
+
|
153 |
+
# Display each chapter with appropriate loading indicators
|
154 |
+
for i, chapter in enumerate(chapters, 1):
|
155 |
+
status = chapter.get(
|
156 |
+
"status", "Creating your magical illustration..."
|
157 |
+
)
|
158 |
+
|
159 |
+
(
|
160 |
+
read_aloud_btn,
|
161 |
+
read_aloud_audio,
|
162 |
+
chapter_content_state,
|
163 |
+
audio_statuss,
|
164 |
+
) = create_chapter_accordion(index=i, chapter=chapter)
|
165 |
+
|
166 |
+
# Add audio generation functionality
|
167 |
+
read_aloud_btn.click(
|
168 |
+
generate_audio_with_status,
|
169 |
+
inputs=[chapter_content_state],
|
170 |
+
outputs=[read_aloud_audio, audio_statuss],
|
171 |
+
)
|
172 |
+
|
173 |
+
return gr.update()
|
174 |
+
else:
|
175 |
+
# If no chapters yet, show a processing message
|
176 |
+
return create_chapter_loading_placeholder()
|
177 |
|
178 |
# Display story title with magical styling
|
179 |
create_story_title_display(
|
|
|
246 |
story_title=story_title,
|
247 |
story_text=story_text,
|
248 |
chapters_state=chapters_state,
|
249 |
+
chapter_loading_container=chapter_loading_container,
|
250 |
)
|
251 |
|
252 |
return demo
|
controllers/app_controller.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
import logging
|
2 |
import json
|
3 |
-
from typing import Optional, List, Any, Union, Tuple
|
4 |
from services.story_generator import generate_story
|
5 |
from services.pdf_text_extractor import extract_text_from_pdf
|
6 |
from services.streaming_chapter_processor import process_story_into_chapters_streaming
|
@@ -135,6 +135,20 @@ def process_chapters(
|
|
135 |
current_data = {"title": story_title, "chapters": chapters}
|
136 |
|
137 |
# Count completed images
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
|
139 |
except Exception as e:
|
140 |
logger.error(f"Error in update callback: {e}")
|
@@ -142,12 +156,12 @@ def process_chapters(
|
|
142 |
return current_data
|
143 |
|
144 |
# Start the streaming process
|
|
|
145 |
process_story_into_chapters_streaming(
|
146 |
story_content, story_title, update_callback=update_callback
|
147 |
)
|
148 |
|
149 |
# Return the final data structure
|
150 |
-
|
151 |
return current_data
|
152 |
|
153 |
except Exception as e:
|
@@ -156,18 +170,82 @@ def process_chapters(
|
|
156 |
|
157 |
|
158 |
# Add chapter processing functionality
|
159 |
-
def handle_chapter_processing(story_content, story_title):
|
160 |
"""Handle chapter processing and update state"""
|
161 |
if not story_content or story_content.startswith("Error:"):
|
162 |
-
return "
|
163 |
-
gr.update(interactive=False)
|
164 |
gr.Info(
|
165 |
message="Processing story into chapters... <br> Go to the Chapters tab to see updates.",
|
166 |
title="Processing",
|
167 |
)
|
|
|
168 |
# Process chapters and return the data structure
|
169 |
-
|
170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
|
173 |
def generate_audio_with_status(text):
|
|
|
1 |
import logging
|
2 |
import json
|
3 |
+
from typing import Optional, List, Any, Union, Tuple, Dict
|
4 |
from services.story_generator import generate_story
|
5 |
from services.pdf_text_extractor import extract_text_from_pdf
|
6 |
from services.streaming_chapter_processor import process_story_into_chapters_streaming
|
|
|
135 |
current_data = {"title": story_title, "chapters": chapters}
|
136 |
|
137 |
# Count completed images
|
138 |
+
total_chapters = len(chapters)
|
139 |
+
completed_images = sum(
|
140 |
+
1 for chapter in chapters if chapter.get("image_b64", "")
|
141 |
+
)
|
142 |
+
|
143 |
+
# Update progress
|
144 |
+
if total_chapters > 0:
|
145 |
+
# First 50% is chapter creation, second 50% is image generation
|
146 |
+
chapter_progress = 0.5 # Chapters are already created at this point
|
147 |
+
image_progress = 0.5 * (completed_images / total_chapters)
|
148 |
+
progress(
|
149 |
+
(chapter_progress + image_progress),
|
150 |
+
f"Generated {completed_images}/{total_chapters} chapter images",
|
151 |
+
)
|
152 |
|
153 |
except Exception as e:
|
154 |
logger.error(f"Error in update callback: {e}")
|
|
|
156 |
return current_data
|
157 |
|
158 |
# Start the streaming process
|
159 |
+
progress(0.05, "Splitting story into chapters...")
|
160 |
process_story_into_chapters_streaming(
|
161 |
story_content, story_title, update_callback=update_callback
|
162 |
)
|
163 |
|
164 |
# Return the final data structure
|
|
|
165 |
return current_data
|
166 |
|
167 |
except Exception as e:
|
|
|
170 |
|
171 |
|
172 |
# Add chapter processing functionality
|
173 |
+
def handle_chapter_processing(story_content, story_title, progress=gr.Progress()):
|
174 |
"""Handle chapter processing and update state"""
|
175 |
if not story_content or story_content.startswith("Error:"):
|
176 |
+
return {"error": "Please generate a valid story first."}
|
|
|
177 |
gr.Info(
|
178 |
message="Processing story into chapters... <br> Go to the Chapters tab to see updates.",
|
179 |
title="Processing",
|
180 |
)
|
181 |
+
|
182 |
# Process chapters and return the data structure
|
183 |
+
logger.info("Starting chapter processing...")
|
184 |
+
progress(0.01, "Starting chapter processing...")
|
185 |
+
|
186 |
+
try:
|
187 |
+
# Store for the current chapters data
|
188 |
+
current_data = {"title": story_title, "chapters": [], "processing": True}
|
189 |
+
|
190 |
+
# Callback function to update the UI with each new chapter image
|
191 |
+
def update_callback(chapters_json):
|
192 |
+
nonlocal current_data
|
193 |
+
try:
|
194 |
+
chapters_data = json.loads(chapters_json)
|
195 |
+
|
196 |
+
# Handle progress updates
|
197 |
+
if "progress" in chapters_data:
|
198 |
+
prog_data = chapters_data["progress"]
|
199 |
+
prog_value = prog_data.get("completed", 0) / prog_data.get(
|
200 |
+
"total", 1
|
201 |
+
)
|
202 |
+
prog_message = prog_data.get("message", "Processing chapters...")
|
203 |
+
progress(prog_value, prog_message)
|
204 |
+
|
205 |
+
# Handle error cases
|
206 |
+
if "error" in chapters_data:
|
207 |
+
current_data = {
|
208 |
+
"title": story_title,
|
209 |
+
"error": chapters_data["error"],
|
210 |
+
}
|
211 |
+
return current_data
|
212 |
+
|
213 |
+
# Update chapters if present
|
214 |
+
if "chapters" in chapters_data:
|
215 |
+
chapters = chapters_data.get("chapters", [])
|
216 |
+
current_data = {
|
217 |
+
"title": story_title,
|
218 |
+
"chapters": chapters,
|
219 |
+
"processing": True,
|
220 |
+
}
|
221 |
+
|
222 |
+
# Check if processing is complete
|
223 |
+
if (
|
224 |
+
"progress" in chapters_data
|
225 |
+
and chapters_data["progress"].get("stage") == "complete"
|
226 |
+
):
|
227 |
+
current_data["processing"] = False
|
228 |
+
|
229 |
+
except Exception as e:
|
230 |
+
logger.error(f"Error in update callback: {e}")
|
231 |
+
current_data = {
|
232 |
+
"title": story_title,
|
233 |
+
"error": f"Error in update: {str(e)}",
|
234 |
+
}
|
235 |
+
|
236 |
+
return current_data
|
237 |
+
|
238 |
+
# Start the streaming process
|
239 |
+
process_story_into_chapters_streaming(
|
240 |
+
story_content, story_title, update_callback=update_callback
|
241 |
+
)
|
242 |
+
|
243 |
+
# Return the final data structure
|
244 |
+
return current_data
|
245 |
+
|
246 |
+
except Exception as e:
|
247 |
+
logger.error(f"Failed to process chapters: {e}", exc_info=True)
|
248 |
+
return {"title": story_title, "error": f"Error processing chapters: {str(e)}"}
|
249 |
|
250 |
|
251 |
def generate_audio_with_status(text):
|
services/streaming_chapter_processor.py
CHANGED
@@ -87,6 +87,18 @@ class StreamingChapterProcessor:
|
|
87 |
str: JSON string with updated chapters as images are generated
|
88 |
"""
|
89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
# Create prompt for splitting the text
|
91 |
prompt = f"""Split the following story into logical chapters (3-5 chapters). And do not include any additional text or explanations. Include all the chapter content.
|
92 |
For each chapter:
|
@@ -111,6 +123,18 @@ Here's the story to split:
|
|
111 |
{story_text}
|
112 |
"""
|
113 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
result = self.mistral_api.send_request(prompt)
|
115 |
logger.info("Getting chapter splitting request from Mistral API")
|
116 |
|
@@ -118,13 +142,36 @@ Here's the story to split:
|
|
118 |
content = result["choices"][0]["message"]["content"]
|
119 |
chapters_data = self._extract_chapters_from_api_response(content)
|
120 |
|
121 |
-
#
|
122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
for chapter in chapters_data:
|
124 |
chapter["image_b64"] = ""
|
|
|
125 |
|
126 |
# First yield the chapters without images
|
127 |
-
yield json.dumps(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
|
129 |
# Start image generation in parallel
|
130 |
with concurrent.futures.ThreadPoolExecutor() as executor:
|
@@ -136,6 +183,7 @@ Here's the story to split:
|
|
136 |
# Keep track of which chapters have been updated
|
137 |
updated_chapters = list(chapters_data)
|
138 |
chapters_remaining = len(chapters_data)
|
|
|
139 |
|
140 |
# Yield updates as images are generated
|
141 |
while chapters_remaining > 0:
|
@@ -143,13 +191,45 @@ Here's the story to split:
|
|
143 |
# Wait for up to 1 second for a new image
|
144 |
index, updated_chapter = self.chapter_queue.get(timeout=1)
|
145 |
updated_chapters[index] = updated_chapter
|
|
|
146 |
chapters_remaining -= 1
|
|
|
147 |
|
148 |
-
#
|
149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
except queue.Empty:
|
151 |
# No new images yet, continue waiting
|
152 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
else:
|
154 |
logger.error("Invalid response format from Mistral API")
|
155 |
yield json.dumps({"error": "Invalid response format from Mistral API"})
|
|
|
87 |
str: JSON string with updated chapters as images are generated
|
88 |
"""
|
89 |
|
90 |
+
# First yield a progress update indicating we're starting
|
91 |
+
yield json.dumps(
|
92 |
+
{
|
93 |
+
"progress": {
|
94 |
+
"stage": "starting",
|
95 |
+
"message": "Starting chapter processing...",
|
96 |
+
"completed": 0,
|
97 |
+
"total": 1,
|
98 |
+
}
|
99 |
+
}
|
100 |
+
)
|
101 |
+
|
102 |
# Create prompt for splitting the text
|
103 |
prompt = f"""Split the following story into logical chapters (3-5 chapters). And do not include any additional text or explanations. Include all the chapter content.
|
104 |
For each chapter:
|
|
|
123 |
{story_text}
|
124 |
"""
|
125 |
try:
|
126 |
+
# Update progress - sending API request
|
127 |
+
yield json.dumps(
|
128 |
+
{
|
129 |
+
"progress": {
|
130 |
+
"stage": "analyzing",
|
131 |
+
"message": "Analyzing story and creating chapters...",
|
132 |
+
"completed": 0.1,
|
133 |
+
"total": 1,
|
134 |
+
}
|
135 |
+
}
|
136 |
+
)
|
137 |
+
|
138 |
result = self.mistral_api.send_request(prompt)
|
139 |
logger.info("Getting chapter splitting request from Mistral API")
|
140 |
|
|
|
142 |
content = result["choices"][0]["message"]["content"]
|
143 |
chapters_data = self._extract_chapters_from_api_response(content)
|
144 |
|
145 |
+
# Update progress - chapters created
|
146 |
+
yield json.dumps(
|
147 |
+
{
|
148 |
+
"progress": {
|
149 |
+
"stage": "chapters_created",
|
150 |
+
"message": "Chapters created! Preparing for image generation...",
|
151 |
+
"completed": 0.3,
|
152 |
+
"total": 1,
|
153 |
+
}
|
154 |
+
}
|
155 |
+
)
|
156 |
+
|
157 |
+
# Add empty image_b64 fields and progress status to chapters
|
158 |
for chapter in chapters_data:
|
159 |
chapter["image_b64"] = ""
|
160 |
+
chapter["status"] = "Waiting for image generation..."
|
161 |
|
162 |
# First yield the chapters without images
|
163 |
+
yield json.dumps(
|
164 |
+
{
|
165 |
+
"chapters": chapters_data,
|
166 |
+
"progress": {
|
167 |
+
"stage": "chapters_created",
|
168 |
+
"message": "Chapters created! Starting image generation...",
|
169 |
+
"completed": 0.4,
|
170 |
+
"total": 1,
|
171 |
+
},
|
172 |
+
},
|
173 |
+
indent=2,
|
174 |
+
)
|
175 |
|
176 |
# Start image generation in parallel
|
177 |
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
|
183 |
# Keep track of which chapters have been updated
|
184 |
updated_chapters = list(chapters_data)
|
185 |
chapters_remaining = len(chapters_data)
|
186 |
+
completed_images = 0
|
187 |
|
188 |
# Yield updates as images are generated
|
189 |
while chapters_remaining > 0:
|
|
|
191 |
# Wait for up to 1 second for a new image
|
192 |
index, updated_chapter = self.chapter_queue.get(timeout=1)
|
193 |
updated_chapters[index] = updated_chapter
|
194 |
+
updated_chapters[index]["status"] = "Image generated!"
|
195 |
chapters_remaining -= 1
|
196 |
+
completed_images += 1
|
197 |
|
198 |
+
# Calculate progress percentage (40% for chapters creation + 60% for image generation)
|
199 |
+
progress_value = 0.4 + (
|
200 |
+
0.6 * completed_images / len(chapters_data)
|
201 |
+
)
|
202 |
+
|
203 |
+
# Yield the updated chapters list with progress information
|
204 |
+
yield json.dumps(
|
205 |
+
{
|
206 |
+
"chapters": updated_chapters,
|
207 |
+
"progress": {
|
208 |
+
"stage": "generating_images",
|
209 |
+
"message": f"Generating chapter images: {completed_images}/{len(chapters_data)} complete",
|
210 |
+
"completed": progress_value,
|
211 |
+
"total": 1,
|
212 |
+
},
|
213 |
+
},
|
214 |
+
indent=2,
|
215 |
+
)
|
216 |
except queue.Empty:
|
217 |
# No new images yet, continue waiting
|
218 |
continue
|
219 |
+
|
220 |
+
# Final progress update
|
221 |
+
yield json.dumps(
|
222 |
+
{
|
223 |
+
"chapters": updated_chapters,
|
224 |
+
"progress": {
|
225 |
+
"stage": "complete",
|
226 |
+
"message": "All chapters and images are ready!",
|
227 |
+
"completed": 1.0,
|
228 |
+
"total": 1,
|
229 |
+
},
|
230 |
+
},
|
231 |
+
indent=2,
|
232 |
+
)
|
233 |
else:
|
234 |
logger.error("Invalid response format from Mistral API")
|
235 |
yield json.dumps({"error": "Invalid response format from Mistral API"})
|
ui/components.py
CHANGED
@@ -250,10 +250,10 @@ def create_story_output_container():
|
|
250 |
|
251 |
def create_chapter_loading_container():
|
252 |
"""Create the loading animation container for chapters"""
|
253 |
-
with gr.Row(visible=False) as container:
|
254 |
gr.HTML("""
|
255 |
-
<div
|
256 |
-
|
257 |
</div>
|
258 |
""")
|
259 |
return container
|
@@ -262,16 +262,36 @@ def create_chapter_loading_container():
|
|
262 |
def create_empty_chapters_placeholder():
|
263 |
"""Create the placeholder for when no chapters are available"""
|
264 |
return gr.HTML("""
|
265 |
-
<div class="image-header-wrapper">
|
266 |
<img src="/gradio_api/file=assets/images/empty_bk.png" style="width: 150px; margin-bottom: 20px;" alt="Empty Book">
|
267 |
<h1 class="sub-header" style="margin-left: 16px; padding-bottom: 36px;">
|
268 |
Your Illustrated Story Awaits!
|
269 |
</h1>
|
270 |
</div>
|
271 |
-
<p class="text" style="font-size: 1.2em; text-align: center;">First, create your story and then click the "Create Illustrated Chapters!" button to see your story come to life with pictures!</p>
|
272 |
""")
|
273 |
|
274 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
def create_chapter_error_display(error_message):
|
276 |
"""Create an error display for chapter generation issues"""
|
277 |
return gr.HTML(f"""
|
@@ -344,7 +364,7 @@ def create_chapter_accordion(index, chapter):
|
|
344 |
gr.HTML("""
|
345 |
<div style="text-align: center; padding: 30px; background-color: #f0f7ff; border-radius: 15px;">
|
346 |
<div class="loading-animation" style="font-size: 30px;">ποΈ</div>
|
347 |
-
<p style="color: #5d9df5; font-weight: bold; margin-top: 15px;">
|
348 |
</div>
|
349 |
""")
|
350 |
|
|
|
250 |
|
251 |
def create_chapter_loading_container():
|
252 |
"""Create the loading animation container for chapters"""
|
253 |
+
with gr.Row(visible=False, elem_id="chapter-loading-container") as container:
|
254 |
gr.HTML("""
|
255 |
+
<div id="progress-info"">
|
256 |
+
<span id="progress-stage">...</span>
|
257 |
</div>
|
258 |
""")
|
259 |
return container
|
|
|
262 |
def create_empty_chapters_placeholder():
|
263 |
"""Create the placeholder for when no chapters are available"""
|
264 |
return gr.HTML("""
|
265 |
+
<div class="image-header-wrapper empty-placeholder" id="empty-chapters-placeholder">
|
266 |
<img src="/gradio_api/file=assets/images/empty_bk.png" style="width: 150px; margin-bottom: 20px;" alt="Empty Book">
|
267 |
<h1 class="sub-header" style="margin-left: 16px; padding-bottom: 36px;">
|
268 |
Your Illustrated Story Awaits!
|
269 |
</h1>
|
270 |
</div>
|
271 |
+
<p class="text empty-placeholder" style="font-size: 1.2em; text-align: center;">First, create your story and then click the "Create Illustrated Chapters!" button to see your story come to life with pictures!</p>
|
272 |
""")
|
273 |
|
274 |
|
275 |
+
def create_chapter_loading_placeholder():
|
276 |
+
return gr.HTML("""
|
277 |
+
<div style="text-align: center; padding: 30px; background-color: #f0f7ff; border-radius: 15px;">
|
278 |
+
<div class="loading-animation" style="font-size: 36px;">β¨πβ¨</div>
|
279 |
+
<h3 style="color: #5d9df5; margin-top: 20px;">Creating Your Magical Story Chapters...</h3>
|
280 |
+
<p style="color: #666; margin-top: 10px;">Our wizard is crafting beautiful chapters and illustrations for your story...</p>
|
281 |
+
<div class="small-loader" style="text-align: center; padding: 10px;">
|
282 |
+
<div class="spinner" style="display: inline-block; width: 20px; height: 20px;
|
283 |
+
border: 3px solid rgba(0, 0, 0, 0.1); border-radius: 50%;
|
284 |
+
border-top-color: #3498db; animation: spin 1s linear infinite;"></div>
|
285 |
+
<style>
|
286 |
+
@keyframes spin {
|
287 |
+
to { transform: rotate(360deg); }
|
288 |
+
}
|
289 |
+
</style>
|
290 |
+
</div>
|
291 |
+
</div>
|
292 |
+
""")
|
293 |
+
|
294 |
+
|
295 |
def create_chapter_error_display(error_message):
|
296 |
"""Create an error display for chapter generation issues"""
|
297 |
return gr.HTML(f"""
|
|
|
364 |
gr.HTML("""
|
365 |
<div style="text-align: center; padding: 30px; background-color: #f0f7ff; border-radius: 15px;">
|
366 |
<div class="loading-animation" style="font-size: 30px;">ποΈ</div>
|
367 |
+
<p style="color: #5d9df5; font-weight: bold; margin-top: 15px;">{status}</p>
|
368 |
</div>
|
369 |
""")
|
370 |
|
ui/events.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2 |
Event handlers for the Magic Story Creator
|
3 |
"""
|
4 |
|
|
|
5 |
from controllers.app_controller import (
|
6 |
process_story_generation,
|
7 |
clear_fields,
|
@@ -25,6 +26,7 @@ def setup_event_handlers(
|
|
25 |
story_title,
|
26 |
story_text,
|
27 |
chapters_state,
|
|
|
28 |
):
|
29 |
"""Set up all the event handlers for the application"""
|
30 |
|
@@ -52,11 +54,33 @@ def setup_event_handlers(
|
|
52 |
outputs=[subject, story_title, story_text, process_chapters_button],
|
53 |
)
|
54 |
|
55 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
process_chapters_button.click(
|
57 |
handle_chapter_processing,
|
58 |
inputs=[story_text, story_title],
|
59 |
outputs=[chapters_state],
|
|
|
60 |
)
|
61 |
|
62 |
return generate_button, clear_button, process_chapters_button
|
|
|
2 |
Event handlers for the Magic Story Creator
|
3 |
"""
|
4 |
|
5 |
+
import gradio as gr
|
6 |
from controllers.app_controller import (
|
7 |
process_story_generation,
|
8 |
clear_fields,
|
|
|
26 |
story_title,
|
27 |
story_text,
|
28 |
chapters_state,
|
29 |
+
chapter_loading_container=None,
|
30 |
):
|
31 |
"""Set up all the event handlers for the application"""
|
32 |
|
|
|
54 |
outputs=[subject, story_title, story_text, process_chapters_button],
|
55 |
)
|
56 |
|
57 |
+
# Function to show loading container and hide placeholder when processing starts
|
58 |
+
def start_chapter_processing(story_text, story_title):
|
59 |
+
# Return initial data structure with processing flag
|
60 |
+
return {"title": story_title, "processing": True, "chapters": []}
|
61 |
+
|
62 |
+
# If we have a loading container reference, show it when processing starts
|
63 |
+
if chapter_loading_container:
|
64 |
+
process_chapters_button.click(
|
65 |
+
lambda: gr.update(visible=True),
|
66 |
+
inputs=[],
|
67 |
+
outputs=[chapter_loading_container],
|
68 |
+
)
|
69 |
+
|
70 |
+
# Show the loading indicator before processing starts and hide placeholder
|
71 |
+
process_chapters_button.click(
|
72 |
+
start_chapter_processing,
|
73 |
+
inputs=[story_text, story_title],
|
74 |
+
outputs=[chapters_state],
|
75 |
+
js="function() { document.querySelectorAll('.empty-placeholder').forEach(el => el.style.display = 'none');window.scrollTo({ top: 0, behavior: 'smooth' }); return []; }",
|
76 |
+
)
|
77 |
+
|
78 |
+
# Chapter processing event handler - happens after loading container is shown
|
79 |
process_chapters_button.click(
|
80 |
handle_chapter_processing,
|
81 |
inputs=[story_text, story_title],
|
82 |
outputs=[chapters_state],
|
83 |
+
show_progress=True, # Show progress bar
|
84 |
)
|
85 |
|
86 |
return generate_button, clear_button, process_chapters_button
|