Spaces:
Sleeping
Sleeping
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
|