Hekaya7 / ui /story_interface.py
XA7's picture
First
fe64b6c
import gradio as gr
import time
from utils.story_management import (
generate_direct_comic,
extract_comic_scenes,
load_narration_from_file
)
from config import IMAGE_STYLES, IMAGE_STYLE_INFO, AGE_GROUPS
from datetime import datetime
def log_execution(func):
def wrapper(*args, **kwargs):
start_time = time.time()
start_str = datetime.fromtimestamp(start_time).strftime('%Y-%m-%d %H:%M:%S')
result = func(*args, **kwargs)
end_time = time.time()
end_str = datetime.fromtimestamp(end_time).strftime('%Y-%m-%d %H:%M:%S')
duration = end_time - start_time
return result
return wrapper
@log_execution
def create_story_interface(demo: gr.Blocks) -> gr.Blocks:
"""Create the main story interface with comic generation functionality.
This function initializes the primary UI interface for the comic generation system,
setting up the main tab structure and components.
Args:
demo (gr.Blocks): The Gradio Blocks instance to build the interface on
Returns:
gr.Blocks: The configured Gradio interface with all components initialized
"""
create_quick_comic_tab()
return demo
def create_quick_comic_tab() -> None:
"""Create a simple tab for direct prompt-to-image comic generation.
Sets up the main comic generation interface with the following components:
- Story prompt input field
- AI prompt enhancement option
- Visual style selection
- Number of scenes selector
- Generation controls
- Image display area
- Scene navigation system
The interface allows users to:
1. Input their story description
2. Configure generation parameters
3. Generate a multi-panel comic
4. View and navigate through individual scenes
"""
with gr.Column():
gr.Markdown("Welcome to Hekaya ")
with gr.Row():
with gr.Column(scale=3):
user_prompt = gr.Textbox(
label="What Hekaya story would you like to visualize?",
placeholder="Describe your story with main characters and settings... (e.g., 'A young wizard learning magic in an ancient castle')",
lines=4
)
enrich_prompt = gr.Checkbox(
label="Enhance prompt with AI for coherence",
value=True,
info="Use AI to add just enough detail and coherence for consistent visual storytelling across all scenes"
)
with gr.Column(scale=1):
comic_style = gr.Dropdown(
label="Visual Style",
choices=IMAGE_STYLES,
value="Comic Book Style"
)
style_description = gr.Markdown(
value=f"*{IMAGE_STYLE_INFO['Comic Book Style']}*",
label="Style Description"
)
age_group = gr.Dropdown(
label="Target Age Group",
choices=AGE_GROUPS,
value="9-12 (Pre-teen)",
info="Select the audience age group. Narration language, detail, and length will adapt automatically."
)
image_quality = gr.Dropdown(
label="Image Quality",
choices=["Low", "Medium", "High"],
value="Low",
info="Select the quality level for generated images. Higher quality may take longer to generate."
)
generate_btn = gr.Button("Generate Hekaya Story", variant="primary")
status_display = gr.Markdown("")
with gr.Row():
with gr.Column(scale=2):
comic_image = gr.Image(label="Generated Hekaya Story", type="filepath")
with gr.Column(scale=1, elem_id="save_info_container"):
gr.Markdown("Your generated story images are automatically saved locally.")
save_path_display = gr.Markdown("", elem_id="save_path_info")
narration_display = gr.Markdown(
visible=True,
elem_id="story_narration",
elem_classes=["story-narration-box"]
)
with gr.Column(visible=False) as scene_viewer_container:
gr.Markdown("Use the navigation buttons to view each upscaled scene individually.")
with gr.Row(equal_height=True):
prev_scene_btn = gr.Button("← Previous Scene", variant="secondary")
scene_counter = gr.Markdown("Scene 1 of 1", elem_id="scene_counter")
next_scene_btn = gr.Button("Next Scene β†’", variant="secondary")
scene_image = gr.Image(label="Current Scene", type="filepath", height=768)
scene_caption_display = gr.Markdown("", elem_id="scene_caption", elem_classes=["scene-caption-box"])
scene_save_path = gr.Markdown("", elem_id="scene_save_path_info")
scene_info = gr.State([])
current_scene_index = gr.State(0)
def update_style_description(style: str) -> str:
"""Update the style description text when a new style is selected."""
return f"*{IMAGE_STYLE_INFO[style]}*"
def show_generating_message() -> str:
"""Display a loading message while story scenes are being generated."""
return "πŸ”„ Generating your story scenes... Please wait..."
def generate_comic_with_length(user_prompt, comic_style, enrich_prompt, age_group, image_quality):
"""Wrapper that handles the fixed num_scenes value while passing the age group and image quality."""
comic_image, save_path_display, status_display, narration = generate_direct_comic(
user_prompt,
comic_style,
12,
enrich_prompt,
3,
age_group,
)
if narration and narration.strip():
narration_formatted = f""
narration_update = gr.update(visible=True, value=narration_formatted)
else:
narration_update = gr.update(visible=True, value="")
return comic_image, save_path_display, status_display, narration_update
def init_scene_viewer(comic_path: str | None) -> tuple:
"""Initialize the scene viewer with extracted scenes from the comic image."""
if not comic_path:
return [], 0, gr.update(visible=False), None, "", "Scene 0 of 0", "No story image generated"
scene_data, save_message = extract_comic_scenes(comic_path, 0)
if not scene_data:
return [], 0, gr.update(visible=False), None, "", "Scene 0 of 0", "Failed to extract scenes"
first_scene = scene_data[0]
return (
scene_data,
0,
gr.update(visible=True),
first_scene['path'],
first_scene['caption'],
f"Scene 1 of {len(scene_data)}",
save_message
)
def update_scene_display(scene_data: list, current_index: int) -> tuple:
"""Update the scene viewer display with the current scene."""
if not scene_data:
return None, "", "Scene 0 of 0"
index = max(0, min(current_index, len(scene_data) - 1))
scene = scene_data[index]
return scene['path'], scene['caption'], f"Scene {index + 1} of {len(scene_data)}"
def navigate_to_previous_scene(idx: int) -> int:
"""Navigate to the previous scene in the sequence."""
return max(0, idx - 1)
def navigate_to_next_scene(paths: list, idx: int) -> int:
"""Navigate to the next scene in the sequence."""
return min(len(paths) - 1, idx + 1) if paths else 0
comic_style.change(
fn=update_style_description,
inputs=[comic_style],
outputs=[style_description]
)
generate_btn.click(
fn=show_generating_message,
inputs=None,
outputs=status_display
).then(
fn=generate_comic_with_length,
inputs=[user_prompt, comic_style, enrich_prompt, age_group, image_quality],
outputs=[comic_image, save_path_display, status_display, narration_display]
).then(
fn=init_scene_viewer,
inputs=[comic_image],
outputs=[
scene_info,
current_scene_index,
scene_viewer_container,
scene_image,
scene_caption_display,
scene_counter,
scene_save_path
]
)
prev_scene_btn.click(
fn=navigate_to_previous_scene,
inputs=[current_scene_index],
outputs=[current_scene_index]
).then(
fn=update_scene_display,
inputs=[scene_info, current_scene_index],
outputs=[scene_image, scene_caption_display, scene_counter]
)
next_scene_btn.click(
fn=navigate_to_next_scene,
inputs=[scene_info, current_scene_index],
outputs=[current_scene_index]
).then(
fn=update_scene_display,
inputs=[scene_info, current_scene_index],
outputs=[scene_image, scene_caption_display, scene_counter]
)