| import base64 |
| import io |
| import os |
| import random |
| from pathlib import Path |
|
|
| import gradio as gr |
| import requests |
| from PIL import Image |
|
|
| from magic_theme import MAGIC_CSS, Magic |
|
|
| BASE_DIR = Path(__file__).resolve().parent |
| PERRI_IMAGE = (BASE_DIR / "perri.png").resolve() |
|
|
| if not PERRI_IMAGE.exists(): |
| raise FileNotFoundError(f"Missing Perri image asset: {PERRI_IMAGE}") |
|
|
|
|
| |
| |
| |
| MODAL_API_URL = os.environ.get( |
| "MODAL_ENDPOINT_URL", |
| "https://meemeealm--perri-comic-pipeline-generate-comic-api.modal.run", |
| ) |
|
|
|
|
| def simulate_comic_generation(user_prompt, color_mode, theme, tone): |
| """ |
| Connects frontend configuration settings to the deployed Modal engine over HTTP. |
| Decodes returned string sequences into a single PIL image asset. |
| """ |
| payload = { |
| "prompt": user_prompt, |
| "color_mode": color_mode, |
| "theme": theme, |
| "tone": tone, |
| } |
|
|
| try: |
| response = requests.post(MODAL_API_URL, json=payload, timeout=150) |
| response.raise_for_status() |
| response_data = response.json() |
|
|
| if response_data.get("status") == "success": |
| panel_data = response_data.get("panel", {}) |
|
|
| img_b64_str = panel_data.get("image_b64", "") |
| if img_b64_str: |
| img_b64_str = img_b64_str.strip() |
| if "," in img_b64_str and img_b64_str.startswith("data:"): |
| img_b64_str = img_b64_str.split(",", 1)[1] |
|
|
| img_bytes = base64.b64decode(img_b64_str) |
| pil_img = Image.open(io.BytesIO(img_bytes)) |
|
|
| return [ |
| gr.update(value=pil_img, visible=True), |
| gr.update(visible=True), |
| gr.update(value="### 🎉 Generation Complete! Your masterpiece is ready below."), |
| ] |
|
|
| except Exception as e: |
| print(f"[Network Pipeline Exception Handled]: {e}") |
|
|
| return [ |
| gr.update(value=None, visible=True), |
| gr.update(visible=False), |
| gr.update(value="#### ⚠️ Connection Timeout or Pipeline Failure. Please check your connection."), |
| ] |
|
|
|
|
| SURPRISE_PROMPTS = [ |
| "A young man asks for a love potion, but accidentally he becomes a cat.", |
| "A knight wants a potion to make his horse fly, but it only makes the horse glow in the dark.", |
| "An old woman wants to see the future, but the alchemist's crystal ball just shows tomorrow's weather.", |
| "A greedy merchant wants a bag to turn stones into gold, but it turns it into explosive popcorn instead.", |
| "A beautiful lady wants to meet the future spouse, so Perri tell her where to go", |
| ] |
|
|
|
|
| def get_random_prompt(): |
| return random.choice(SURPRISE_PROMPTS) |
|
|
|
|
| def reveal_workspace(): |
| return gr.update(visible=False), gr.update(visible=True) |
|
|
|
|
| UNLOCK_WORKSPACE_JS = """ |
| () => { |
| document.documentElement.classList.add('workspace-unlocked'); |
| document.body.classList.add('workspace-unlocked'); |
| } |
| """ |
|
|
|
|
| |
| |
| |
| def build_greeter_card(perri_image_path): |
| with gr.Column(visible=True, elem_classes="greeter-shell") as greeter_shell: |
| with gr.Row(elem_classes="greeter-card"): |
| with gr.Column(scale=1, elem_classes="greeter-portrait"): |
| gr.Image( |
| value=str(perri_image_path), |
| show_label=False, |
| interactive=False, |
| ) |
| with gr.Column(scale=2, elem_classes="greeter-copy"): |
| gr.Markdown( |
| """ |
| <h2>Welcome to the comic workshop ...</h2> |
| <p>I'm Perri, an alchemist. I am thoughtful, energetic and sometimes humorous. I will guide you through the comic forge.</p> |
| <p>Give me a story seed, and I'll help you turn it into a polished comic strip.</p> |
| """ |
| ) |
| with gr.Row(elem_classes="greeter-actions"): |
| enter_btn = gr.Button("Enter the workshop", variant="primary") |
|
|
| return greeter_shell, enter_btn |
|
|
|
|
| with gr.Blocks() as demo: |
| greeter_shell, enter_btn = build_greeter_card(PERRI_IMAGE) |
|
|
| with gr.Column(visible=False, elem_classes="container") as workspace_shell: |
| gr.Markdown( |
| """ |
| # A day of Perri, The Alchemist |
| Let's create unique, retro comic strips featuring **Perri**. |
| """ |
| ) |
|
|
| with gr.Accordion("Collapsible Guidebook & Prompting Tips", open=False): |
| gr.Markdown( |
| """ |
| ### How to get the best comic strips: |
| * **Keep it narrative:** Describe a clear cause-and-effect situation, such as someone wanting X but/and the Alchemist/someone doing Y. |
| * **Character Focus:** The AI understands 'The Alchemist' as a wise, slightly chaotic lady with a messy workshop. |
| * **Tone Impact:** Choosing Philisophical vs Satire changes how the text model interprets the ending of your story. |
| |
| ### Example Prompts to Try: |
| 1. `Perri wants a love potion for his crush, but it made him a colorful cat.` |
| 2. `A warrior asks for an elixir of eternal strength before a tournament, and he became a dragon.` |
| 3. `A thief begs for an invisibility potion to rob the royal vault, but he becomes a duck.` |
| 4. `A beautiful lady wants to meet the future spouse, so Perri tells her where to go.` |
| """ |
| ) |
|
|
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### Comic Controls") |
|
|
| with gr.Group(): |
| user_prompt = gr.Textbox( |
| label="What is the story outline?", |
| placeholder="e.g., Annie want to see their future fortunes...", |
| lines=3, |
| container=False, |
| ) |
|
|
| with gr.Row(): |
| surprise_btn = gr.Button( |
| "Pre-defined prompt", |
| elem_classes="filter-btn", |
| size="sm", |
| ) |
|
|
| color_mode = gr.Radio( |
| choices=["Black and White", "Full Color"], |
| value="Black and White", |
| label="Visual Aesthetic", |
| ) |
|
|
| theme = gr.Dropdown( |
| choices=["Mortality", "Potion", "Fortune-telling", "Wealth"], |
| value="Potion", |
| label="Story Theme", |
| ) |
|
|
| tone = gr.Dropdown( |
| choices=["Humor", "Satire", "Dark", "Inspirational", "Caring", "Philosophical"], |
| value="Humor", |
| label="Story Narrative Tone", |
| ) |
|
|
| generate_btn = gr.Button("Generate Comic Strip", variant="primary") |
|
|
| with gr.Column(scale=2): |
| gr.Markdown("### The Comic Canvas") |
| status_tracker = gr.Markdown("*Your masterpiece will appear below when ready.*") |
|
|
| p1 = gr.Image(label="Comic Panel", |
| interactive=False, |
| visible=True, |
| height=300, |
| width=400) |
| download_btn = gr.Button("Download Whole Image (.png)", variant="success", visible=False) |
|
|
| |
| |
| |
| enter_btn.click( |
| fn=None, |
| inputs=None, |
| outputs=None, |
| js=UNLOCK_WORKSPACE_JS, |
| ) |
|
|
| enter_btn.click( |
| fn=reveal_workspace, |
| inputs=None, |
| outputs=[greeter_shell, workspace_shell], |
| ) |
|
|
| surprise_btn.click( |
| fn=get_random_prompt, |
| inputs=None, |
| outputs=user_prompt, |
| ) |
|
|
| generate_btn.click( |
| fn=simulate_comic_generation, |
| inputs=[user_prompt, color_mode, theme, tone], |
| outputs=[p1, download_btn, status_tracker], |
| ) |
|
|
|
|
| |
| |
| |
| if __name__ == "__main__": |
| demo.launch(theme=Magic(), css=MAGIC_CSS) |
|
|