File size: 8,536 Bytes
633c7e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
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}")


# ==========================================
# 2. NETWORKING ENGINE BRIDGE
# ==========================================
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');
}
"""


# ==========================================
# 3. INTERFACE COMPOSITION
# ==========================================
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)

    # ==========================================
    # 4. GLOBAL EVENT CONTROLLER WIRE-UP
    # ==========================================
    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],
    )


# ==========================================
# 5. APPLICATION RUNTIME RUN
# ==========================================
if __name__ == "__main__":
    demo.launch(theme=Magic(), css=MAGIC_CSS)