File size: 10,429 Bytes
db30d92
 
 
 
 
5ec2bf4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db30d92
 
5ec2bf4
 
 
db30d92
 
 
5ec2bf4
 
 
db30d92
 
 
5ec2bf4
 
 
db30d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ec2bf4
 
 
 
db30d92
5ec2bf4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db30d92
 
 
5ec2bf4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db30d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ec2bf4
db30d92
 
5ec2bf4
 
 
db30d92
 
 
 
 
 
 
 
 
 
 
 
5ec2bf4
 
 
 
 
 
 
 
 
 
 
 
db30d92
 
 
 
 
 
 
 
5ec2bf4
 
 
db30d92
5ec2bf4
 
db30d92
5ec2bf4
db30d92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ec2bf4
db30d92
5ec2bf4
db30d92
 
 
 
 
 
 
5ec2bf4
 
 
 
 
 
db30d92
5ec2bf4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db30d92
 
5ec2bf4
 
 
 
 
db30d92
5ec2bf4
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
"""
Dispatch AI — Meme Generator
Gradio app for AI/tech memes. Top+bottom caption input, FLUX image gen, PIL text overlay.
"""

import os
import io
import random
import gradio as gr
from huggingface_hub import InferenceClient
from PIL import Image, ImageDraw, ImageFont

# --- Configuration -----------------------------------------------------------
HF_TOKEN = os.environ.get("HF_TOKEN", None)
MODEL_ID = "black-forest-labs/FLUX.1-schnell"

client = InferenceClient(model=MODEL_ID, token=HF_TOKEN)

BG_COLOR = "#0A0F1A"
ACCENT = "#1FE0E6"

# Preset templates: name -> (base_image_prompt, default_top, default_bottom)
TEMPLATES = {
    "This is Fine AI": (
        "A cartoon character sitting in a burning AI server room surrounded by flames and melting GPUs, "
        "smiling calmly and saying 'this is fine', meme style, comic illustration, white background panels",
        "MY GPU IS ON FIRE",
        "THIS IS FINE",
    ),
    "Distracted Developer": (
        "A meme photo of a developer walking with his laptop but looking back at a passing shiny new robot "
        "labeled 'NEW AI MODEL', laptop labeled 'MY CURRENT MODEL', meme style photo",
        "MY CURRENT MODEL",
        "NEW HYPED AI MODEL",
    ),
    "Drake Format": (
        "Drake hotline bling meme format two-panel image, top panel Drake disapproving with hand up, "
        "bottom panel Drake approving pointing finger, meme template",
        "WRITING CODE MYSELF",
        "ASKING AI TO DO IT",
    ),
    "Expanding Brain": (
        "Expanding brain meme format, four panels showing progressively glowing brighter brains, meme template",
        "USING AI",
        "FINE-TUNING AI ON MY DATA",
    ),
    "Galaxy Brain": (
        "Galaxy brain meme format, three panels with progressively more cosmic brain, meme template",
        "PROMPTING CHATGPT",
        "TRAINING MY OWN MODEL",
    ),
    "Stonks": (
        "Stonks meme format, crudely drawn man in suit standing in front of upward arrow chart, meme meme",
        "CLOUD API COSTS",
        "ON-DEVICE INFERENCE",
    ),
    "Two Buttons": (
        "Two buttons meme format, sweating man deciding between two red buttons, meme template",
        "PAY $0.06 PER 1K TOKENS",
        "RUN ON MY PHONE",
    ),
    "Is This a Pigeon": (
        "Is this a pigeon meme format, anime character looking at butterfly confused, meme template",
        "IS THIS AN AGENT?",
        "IT'S A WHILE LOOP",
    ),
}

# Random AI topics for the Random AI Topic button
AI_TOPICS = [
    "AGI is coming next year (again)",
    "My model hallucinated a whole paper",
    "Prompt engineering is real engineering",
    "I fine-tuned on my DMs",
    "The model refused my prompt",
    "I ran out of GPU credits",
    "My agent looped forever",
    "Context window too small for my TODO list",
    "Open source beat closed source",
    "Inference costs more than my rent",
    "My dataset was just vibes",
    "RLHF made my bot too polite",
    "I forgot to clip the gradients",
    "The model learned to lie",
    "Quantization ruined my quality",
    "My LoRA saved my marriage",
    "Distillation taught the student to cheat",
    "The tokenizer hates Arabic",
    "I spent $0 on training and won the benchmark",
    "Agents are just while loops with vibes",
    "My gradient exploded and so did my career",
    "I asked the model to debug itself",
    "The embedding space is just vibes in high dimensions",
]

# Try to find a usable font
FONT_CANDIDATES = [
    "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
    "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
    "C:\\Windows\\Fonts\\arialbd.ttf",
    "arial.ttf",
]


def load_font(size):
    for path in FONT_CANDIDATES:
        if os.path.exists(path):
            return ImageFont.truetype(path, size)
    return ImageFont.load_default()


def random_topic():
    return random.choice(AI_TOPICS)


# --- Core function -----------------------------------------------------------
def generate_meme(top_caption, bottom_caption, template_name):
    """Generate a meme image: generate base via FLUX, then overlay text."""
    if template_name and template_name in TEMPLATES:
        base_prompt, def_top, def_bot = TEMPLATES[template_name]
        top = top_caption.strip() if top_caption.strip() else def_top
        bot = bottom_caption.strip() if bottom_caption.strip() else def_bot
    else:
        base_prompt = "A humorous meme style illustration related to artificial intelligence, comic style"
        top = top_caption.strip() if top_caption.strip() else "TOP TEXT"
        bot = bottom_caption.strip() if bottom_caption.strip() else "BOTTOM TEXT"

    w, h = 1024, 1024

    try:
        image = client.text_to_image(base_prompt, width=w, height=h)
        if not isinstance(image, Image.Image):
            image = Image.open(io.BytesIO(image)) if hasattr(image, "read") else Image.open(image)
    except Exception as e:
        image = Image.new("RGB", (w, h), BG_COLOR)
        d = ImageDraw.Draw(image)
        d.text((20, h // 2), f"Error: {e}", fill=ACCENT)

    image = overlay_meme_text(image, top.upper(), bot.upper())
    return image, f"✅ Meme generated! Top: '{top}' | Bottom: '{bot}'"


def overlay_meme_text(img, top_text, bottom_text):
    """Draw classic Impact-font style meme text with black outline."""
    img = img.convert("RGB")
    draw = ImageDraw.Draw(img)
    W, H = img.size

    font_size = max(24, int(W / 12))
    font = load_font(font_size)

    def wrap_text(text, max_chars):
        words = text.split()
        lines = []
        current = ""
        for word in words:
            if len(current + " " + word) <= max_chars:
                current = current + " " + word if current else word
            else:
                if current:
                    lines.append(current)
                current = word
        if current:
            lines.append(current)
        return lines

    def draw_text_with_outline(text, y, anchor="ma"):
        for dx in (-3, -2, -1, 1, 2, 3):
            for dy in (-3, -2, -1, 1, 2, 3):
                draw.text((W // 2 + dx, y + dy), text, fill="black", font=font, anchor=anchor)
        draw.text((W // 2, y), text, fill="white", font=font, anchor=anchor)

    # Top text
    max_chars = int(W / (font_size * 0.55))
    top_lines = wrap_text(top_text, max_chars)
    for i, line in enumerate(top_lines):
        draw_text_with_outline(line, int(H * 0.06) + i * (font_size + 4), anchor="ma")

    # Bottom text
    bot_lines = wrap_text(bottom_text, max_chars)
    total_h = len(bot_lines) * (font_size + 4)
    for i, line in enumerate(bot_lines):
        y = int(H * 0.94) - total_h + i * (font_size + 4) + font_size
        draw_text_with_outline(line, y, anchor="ma")

    return img


def load_template(template_name):
    if template_name and template_name in TEMPLATES:
        _, def_top, def_bot = TEMPLATES[template_name]
        return def_top, def_bot
    return "", ""


# --- UI -----------------------------------------------------------------------
CSS = """
#dispatch-header h1 {
    color: #FFFFFF; font-size: 2.2rem; margin: 0;
    background: linear-gradient(90deg, #1FE0E6 0%, #FFFFFF 60%);
    -webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
#dispatch-header p { color: #1FE0E6; font-size: 1.05rem; margin: 6px 0 0 0; }
.dispatch-footer { text-align: center; color: #8A8F9C; font-size: 0.9rem; padding-top: 8px; }
"""

with gr.Blocks(
    title="Dispatch AI — Meme Generator",
    theme=gr.themes.Base(
        primary_hue="cyan",
        secondary_hue="cyan",
        neutral_hue="slate",
        font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui"],
    ).set(
        body_background_fill="#0A0F1A",
        body_background_fill_dark="#0A0F1A",
        body_text_color="#FFFFFF",
        body_text_color_dark="#FFFFFF",
        block_background_fill="#0E1424",
        block_background_fill_dark="#0E1424",
        block_border_color="#1FE0E6",
        block_border_width="1px",
        block_label_text_color="#1FE0E6",
        block_title_text_color="#1FE0E6",
        button_primary_background_fill="#1FE0E6",
        button_primary_background_fill_dark="#1FE0E6",
        button_primary_text_color="#0A0F1A",
        button_primary_border_color="#1FE0E6",
        input_background_fill="#0E1424",
        input_background_fill_dark="#0E1424",
        input_border_color="#1FE0E6",
        input_border_width="1px",
    ),
    css=CSS,
) as demo:
    with gr.Column(elem_id="dispatch-header"):
        gr.Markdown(
            """
            # Dispatch AI — Meme Generator
            Generate AI/tech memes with FLUX.1-schnell + PIL text overlay · Dispatch AI (FZE) · UAE
            """
        )

    with gr.Row():
        with gr.Column(scale=1):
            template_select = gr.Dropdown(
                list(TEMPLATES.keys()),
                label="Meme Template",
                value="This is Fine AI",
                info="Pick a template to auto-fill captions and base image",
            )
            load_template_btn = gr.Button("Load Template Captions", variant="secondary")
            top_caption = gr.Textbox(label="Top Caption", placeholder="TOP TEXT", lines=1)
            bottom_caption = gr.Textbox(label="Bottom Caption", placeholder="BOTTOM TEXT", lines=1)
            random_topic_btn = gr.Button("🎲 Random AI Topic", variant="secondary")
            topic_box = gr.Textbox(label="Random AI Topic", interactive=True, lines=1)
            generate_btn = gr.Button("🤣 Generate Meme", variant="primary")
        with gr.Column(scale=2):
            output_image = gr.Image(label="Generated Meme", type="pil", show_download_button=True)
            status_box = gr.Textbox(label="Status", interactive=False)

    # Events
    load_template_btn.click(load_template, inputs=template_select, outputs=[top_caption, bottom_caption])
    random_topic_btn.click(random_topic, outputs=topic_box)
    generate_btn.click(
        generate_meme,
        inputs=[top_caption, bottom_caption, template_select],
        outputs=[output_image, status_box],
    )

    gr.Markdown(
        """
        <div class="dispatch-footer">
        © 2026 Dispatch AI (FZE) · UAE · License 10818 · Model: FLUX.1-schnell · Text overlay via Pillow
        </div>
        """
    )

if __name__ == "__main__":
    demo.queue()
    demo.launch()