Plateur / app.py
shukdev3's picture
Update app.py
e6190bc verified
Raw
History Blame Contribute Delete
31.6 kB
import gradio as gr
import tempfile
import time
import os
from huggingface_hub import InferenceClient
# ── Constants ─────────────────────────────────────────────────────────────────
FLUX_MODEL = "black-forest-labs/FLUX.1-schnell"
# ── Load token from HF Space secret ──────────────────────────────────────────
HF_TOKEN = os.environ.get("HF_TOKEN", "")
SIZE_OPTIONS = {
"1:1 Square (1024Γ—1024)": (1024, 1024),
"4:3 Landscape (1152Γ—896)": (1152, 896),
"3:4 Portrait (896Γ—1152)": (896, 1152),
"16:9 Wide (1344Γ—768)": (1344, 768),
"9:16 Tall (768Γ—1344)": (768, 1344),
}
STYLE_PRESETS = {
"πŸ‘¨πŸ»β€πŸ³ Fine Dining":
"Michelin-star restaurant editorial plating, pristine white bone-china plate, "
"dramatic chiaroscuro side-lighting, 85mm portrait lens shallow depth of field, "
"microgreens and edible flowers garnish, polished dark marble surface, "
"deep shadow fill, luxury fine-dining atmosphere, hyper-sharp food detail",
"πŸ•―οΈ Moody & Dark":
"dark moody atmospheric food photography, aged oak table, single warm candlelight source, "
"deep chestnut and obsidian tones, heavy shadow drama, rich jewel-toned background, "
"wisps of steam rising, cinematic Rembrandt lighting, editorial gourmet aesthetic, "
"50mm f/1.4 lens, vignette edges, brooding and indulgent mood",
"β˜€οΈ Bright & Airy":
"bright airy lifestyle food photography, white Carrara marble countertop, "
"soft diffused morning window light from the left, pale pastel linen napkin, "
"fresh botanicals and citrus accents, overexposed highlights, "
"clean white negative space, 35mm lens, cheerful and fresh Instagram aesthetic, "
"light and breezy Scandinavian mood",
"🌿 Fresh & Minimal":
"minimalist zen food photography, light grey concrete slab surface, "
"soft natural north-facing diffused light, generous intentional negative space, "
"single hero ingredient as focal point, dewy fresh herbs accent, "
"ultra-sharp macro detail on textures, muted earthy neutral palette, "
"Japanese wabi-sabi aesthetic, 100mm macro lens, contemplative and clean",
"🏑 Rustic Farmhouse":
"rustic farmhouse food photography, reclaimed weathered wood table, "
"warm golden-hour side light through linen curtains, linen and burlap napkins, "
"cast iron skillet, terracotta ceramics, wildflower sprig accent, "
"organic imperfect textures, amber and sienna warm tones, "
"nostalgic cozy harvest atmosphere, 50mm lens natural light",
"πŸ“Έ Overhead Flat Lay":
"overhead 90-degree flat lay food photography, pure white marble surface, "
"soft even studio lighting, zero shadows, symmetrical hero composition, "
"food styled with complementary props: linen napkin, vintage cutlery, small ceramic bowl, "
"negative space for text, sharp corner-to-corner focus, "
"clean editorial top-down perspective, shot on Hasselblad, commercial menu photography",
"πŸ”₯ BBQ & Smoke":
"smoky outdoor BBQ grill food photography, charred cast iron grates, "
"dramatic ember glow from below, wisps of smoke curling upward, "
"charred grill marks hero detail, rustic weathered picnic wood table, "
"warm fiery amber and sienna tones, condensation on nearby cold glass, "
"summer cookout atmosphere, golden-hour back light, 50mm lens wide aperture",
"🍰 Pastel Patisserie":
"French patisserie editorial food photography, soft sage or blush pink background, "
"matte white ceramic plate, diffused overcast studio lighting, "
"powdered sugar dusting detail, glossy mirror glaze reflection, "
"delicate edible flowers and gold leaf accents, "
"pastel macarons or petit fours as props, 85mm macro lens, "
"dreamy soft-focus bokeh, elegant Parisian confectionery mood",
}
EXAMPLES = [
# ── 1. Fine Dining ────────────────────────────────────────────────────────
[
"Wagyu beef tenderloin, black truffle, parsnip purΓ©e, baby spinach, "
"red wine jus, microgreens, edible gold leaf, fleur de sel",
"Perfectly seared Wagyu tenderloin medallion resting on a silky parsnip purΓ©e, "
"crowned with paper-thin black truffle slices and edible gold leaf, "
"a glossy red wine jus pooled artfully beside it, finished with microgreens "
"and a pinch of fleur de sel on a pristine white bone-china plate.",
"πŸ‘¨πŸ»β€πŸ³ Fine Dining",
"1:1 Square (1024Γ—1024)",
],
# ── 2. Moody & Dark ───────────────────────────────────────────────────────
[
"Beef short ribs, red wine, bone broth, garlic, thyme, rosemary, "
"creamy mashed potatoes, crispy shallots, fresh horseradish",
"Slow-braised beef short rib collapsed off the bone, lacquered in a deep "
"garnet red wine reduction, served over ivory mashed potato, "
"scattered with crispy golden shallots and a fresh horseradish snow, "
"steam rising from the plate into candlelit shadow.",
"πŸ•―οΈ Moody & Dark",
"4:3 Landscape (1152Γ—896)",
],
# ── 3. Bright & Airy ──────────────────────────────────────────────────────
[
"AΓ§aΓ­, frozen banana, almond milk, granola, fresh strawberries, "
"blueberries, kiwi, mango, coconut flakes, honey, chia seeds",
"Vibrant deep-purple aΓ§aΓ­ smoothie bowl topped with neat rows of "
"sliced golden mango, fresh kiwi, juicy strawberries and blueberries, "
"a sprinkle of toasted granola and coconut flakes, finished with a "
"honey drizzle β€” served in a white ceramic bowl on a sunlit marble counter.",
"β˜€οΈ Bright & Airy",
"1:1 Square (1024Γ—1024)",
],
# ── 4. Fresh & Minimal ────────────────────────────────────────────────────
[
"Sashimi-grade salmon, Japanese cucumber, avocado, pickled daikon, "
"yuzu juice, toasted sesame seeds, shiso leaf, soy sauce, wasabi",
"Three pristine salmon sashimi slices fanned over a paper-thin cucumber ribbon, "
"a halved avocado rose alongside, single shiso leaf accent, "
"yuzu drops scattered across the surface, sesame seeds dusted lightly β€” "
"composed with precise negative space on a cool light-grey concrete slab.",
"🌿 Fresh & Minimal",
"4:3 Landscape (1152Γ—896)",
],
# ── 5. Rustic Farmhouse ───────────────────────────────────────────────────
[
"Whole roasted chicken, root vegetables, carrots, parsnips, "
"turnips, fresh thyme, rosemary, garlic bulb, chicken gravy, sea salt",
"Golden whole roasted chicken glistening in herb-infused pan drippings, "
"surrounded by caramelised carrots, parsnips and turnips in a well-seasoned "
"cast-iron roasting dish, a split roasted garlic bulb alongside, "
"fresh thyme and rosemary scattered over the top, rich gravy pooling around the base.",
"🏑 Rustic Farmhouse",
"4:3 Landscape (1152Γ—896)",
],
# ── 6. Overhead Flat Lay ──────────────────────────────────────────────────
[
"Hummus, warm pita bread, kalamata olives, roasted red peppers, "
"crumbled feta, tabbouleh, stuffed grape leaves, za'atar, olive oil, lemon",
"Abundant Mediterranean mezze spread photographed directly overhead β€” "
"a large bowl of creamy hummus with olive oil pool at the centre, "
"surrounded by golden warm pita wedges, glossy olives, roasted peppers, "
"crumbled feta, vibrant tabbouleh and stuffed grape leaves, "
"the whole spread filling the frame edge to edge on white marble.",
"πŸ“Έ Overhead Flat Lay",
"4:3 Landscape (1152Γ—896)",
],
# ── 7. BBQ & Smoke ────────────────────────────────────────────────────────
[
"Beef brisket, dry rub spices, smoked paprika, brown sugar, garlic, "
"apple cider vinegar, BBQ sauce, coleslaw, pickles, brioche bun",
"Thick-sliced smoked beef brisket with a deep mahogany bark crust, "
"bark cracking open to reveal a pink smoke ring and tender pull-apart interior, "
"stacked beside creamy coleslaw and tangy pickles on butcher paper, "
"a ramekin of glossy BBQ sauce alongside, ember glow lighting the scene from behind.",
"πŸ”₯ BBQ & Smoke",
"4:3 Landscape (1152Γ—896)",
],
# ── 8. Pastel Patisserie ──────────────────────────────────────────────────
[
"French macarons, almond flour, egg whites, sugar, raspberry jam, "
"rose buttercream, lavender, vanilla, edible dried rose petals, gold dust",
"A delicate tower of pastel French macarons β€” blush rose, "
"soft lavender and vanilla cream β€” stacked on a white ceramic cake stand, "
"edible dried rose petals scattered at the base, a dusting of gold "
"catching the soft diffused studio light, a single macaron cracked open "
"revealing the thick rose buttercream centre on a sage-green backdrop.",
"🍰 Pastel Patisserie",
"1:1 Square (1024Γ—1024)",
],
]
# ── Core generation ───────────────────────────────────────────────────────────
def generate_dish_image(ingredients, description, style_label, size_label, steps, guidance):
if not HF_TOKEN:
return None, "❌ HF_TOKEN secret is not set. Add it in Space Settings β†’ Variables and secrets."
if not ingredients.strip():
return None, "❌ Please list at least one ingredient."
if not description.strip():
return None, "❌ Please describe the dish."
width, height = SIZE_OPTIONS.get(size_label, (1024, 1024))
style_suffix = STYLE_PRESETS.get(style_label, "")
prompt = (
f"Professional food photography of: {description.strip()}. "
f"Ingredients: {ingredients.strip()}. "
f"{style_suffix}. "
f"Mouth-watering, ultra-detailed, award-winning culinary photography, 8K resolution."
)
try:
t0 = time.time()
client = InferenceClient(
provider="hf-inference",
api_key=HF_TOKEN,
)
image = client.text_to_image(
prompt,
model=FLUX_MODEL,
width=width,
height=height,
num_inference_steps=int(steps),
guidance_scale=float(guidance),
)
elapsed = time.time() - t0
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
image.save(tmp.name)
tmp.close()
return tmp.name, (
f"βœ… Generated in {elapsed:.1f}s Β· FLUX.1-schnell Β· "
f"{width}Γ—{height} Β· {int(steps)} steps Β· FREE πŸŽ‰"
)
except Exception as exc:
err = str(exc)
if "401" in err or "unauthorized" in err.lower():
return None, "❌ Invalid token. Check your HF_TOKEN secret."
if "403" in err or "forbidden" in err.lower():
return None, (
"❌ 403 Forbidden β€” enable 'Make calls to Inference Providers' "
"permission on your token at huggingface.co/settings/tokens"
)
if "429" in err:
return None, "❌ Rate limit hit. Wait a moment then try again."
return None, f"❌ Error: {err[:300]}"
# ── CSS ───────────────────────────────────────────────────────────────────────
css = """
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,400&family=Outfit:wght@300;400;500;600;700&display=swap');
*, *::before, *::after { box-sizing: border-box; }
body, .gradio-container {
font-family: 'Outfit', sans-serif !important;
background: #080706 !important;
color: #e8e0d5 !important;
}
/* ── Hero ── */
.hero {
text-align: center;
padding: 52px 32px 40px;
margin-bottom: 4px;
border-radius: 20px;
background: linear-gradient(160deg, #625b28 0%, #1a160e 40%, #423215 100%) !important;
border: 1px solid rgba(200, 150, 60, 0.18);
box-shadow: 0 24px 64px rgba(0,0,0,0.6);
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: -80px; left: 50%; transform: translateX(-50%);
width: 500px; height: 200px;
background: radial-gradient(ellipse, rgba(220,160,60,0.14) 0%, transparent 65%);
pointer-events: none;
}
.hero-eyebrow {
font-size: 0.72rem; font-weight: 600;
letter-spacing: 0.22em; text-transform: uppercase;
color: #c8933a !important; margin-bottom: 14px;
}
.hero h1 {
font-family: 'Cormorant Garamond', serif !important;
font-size: clamp(2.4rem, 5vw, 3.8rem) !important;
font-weight: 600 !important; color: #f2e8d8 !important;
line-height: 1.1 !important; margin-bottom: 12px !important;
}
.hero h1 em { font-style: italic; color: #d4974a !important; }
.hero-sub { font-size: 0.95rem; color: #9a8e7e !important; font-weight: 300; }
.hero-badges {
display: flex; justify-content: center;
gap: 10px; flex-wrap: wrap; margin-top: 20px;
}
.badge {
display: inline-flex; align-items: center; gap: 6px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.09);
border-radius: 999px; padding: 5px 14px;
font-size: 0.74rem; font-weight: 500; color: #9e8e7a !important;
}
.badge.green { border-color: rgba(95,207,122,0.3); color: #5fcf7a; background: rgba(95,207,122,0.07); }
/* ── Cards ── */
.card {
background: #0f0d0a;
border: 1px solid rgba(200,150,60,0.13);
border-radius: 18px; padding: 24px 22px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
.card-title {
margin-top: 10px !important;
margin-left: 5px !important;
font-family: 'Cormorant Garamond', serif !important;
font-size: 1.2rem !important; font-weight: 700 !important;
color: rgb(29 168 62) !important;
border-bottom: 1px solid rgba(200,150,60,0.18) !important;
padding-bottom: 12px !important;
display: flex !important; align-items: center !important; gap: 8px !important;
}
/* ── Field labels ── */
label span, .gr-form label span {
font-family: 'Outfit', sans-serif !important;
font-size: 0.8rem !important; font-weight: 600 !important;
letter-spacing: 0.1em !important; text-transform: uppercase !important;
color: #8c6922 !important;
}
/* ── Inputs ── */
textarea, input[type="text"], input[type="password"], input[type="number"] {
background: #D4974A !important;
border: 1px solid rgba(200,150,60,0.15) !important;
border-radius: 12px !important; color: #ede5d8 !important;
font-family: 'Outfit', sans-serif !important;
font-size: 0.94rem !important; font-weight: 400 !important;
transition: border-color 0.2s, box-shadow 0.2s !important;
}
textarea:focus, input:focus {
border-color: rgba(200,150,60,0.45) !important;
box-shadow: 0 0 0 3px rgba(200,150,60,0.08) !important;
}
textarea::placeholder, input::placeholder {
color: #4a4038 !important; font-style: italic !important;
}
input[type="range"] { accent-color: #c8933a !important; }
/* ── Setting explainer boxes ── */
.setting-explainer {
background: rgba(200,150,60,0.06);
border: 1px solid rgba(200,150,60,0.14);
border-radius: 10px; padding: 12px 14px;
margin-top: 10px; margin-bottom: 4px;
}
.setting-explainer .label {
font-size: 0.72rem; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.1em;
color: #996f29; margin-bottom: 4px;
}
.setting-explainer .text {
font-size: 0.84rem; color: #b0a090;
line-height: 1.55; font-weight: 300;
color: black !important;
}
.setting-explainer .text strong { color: #8c6922; font-weight: 600; }
/* ── Two-slider row inside full-width settings card ── */
.sliders-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
align-items: start;
}
.slider-block { display: flex; flex-direction: column; }
/* ── Generate button ── */
.gen-btn {
background: linear-gradient(135deg, #b8832a 0%, #e8bb5a 45%, #c8902e 100%) !important;
border: none !important; border-radius: 14px !important;
color: #0a0805 !important; font-family: 'Outfit', sans-serif !important;
font-size: 1rem !important; font-weight: 700 !important;
letter-spacing: 0.07em !important; text-transform: uppercase !important;
padding: 17px 0 !important; width: 100% !important; cursor: pointer !important;
transition: transform 0.15s, box-shadow 0.25s !important;
box-shadow: 0 4px 24px rgba(200,150,60,0.3) !important; margin-top: 6px !important;
}
.gen-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 8px 36px rgba(200,150,60,0.42) !important; }
.gen-btn:active { transform: translateY(0) !important; }
/* ── Image output ── */
.img-out img {
border-radius: 14px !important;
border: 1px solid rgba(200,150,60,0.18) !important;
width: 100% !important; object-fit: cover !important;
box-shadow: 0 16px 48px rgba(0,0,0,0.6) !important;
}
.status-out textarea {
font-size: 0.88rem !important; color: #d4a650 !important;
background: #0a0806 !important;
border-color: rgba(200,150,60,0.12) !important; font-weight: 500 !important;
}
/* ── Info bar ── */
.info-bar {
background: #0c0a08; border: 1px solid rgba(200,150,60,0.12);
border-radius: 14px; padding: 16px 22px;
display: flex; gap: 22px; flex-wrap: wrap; align-items: center;
}
.info-item { font-size: 0.82rem; color: white !important; }
.info-item strong { color: #b8a880 !important; font-weight: 600; }
.info-item.free strong { color: #5fcf7a !important; }
/* ── Examples table ── */
.examples-label {
font-size: 0.76rem; font-weight: 700;
letter-spacing: 0.15em; text-transform: uppercase;
color: #d4a650 !important; margin: 0 0 8px 2px;
}
.gr-examples, [id*="examples"], .svelte-examples,
div[class*="examples"] {
background: #131008 !important;
border: 1px solid rgba(200,150,60,0.2) !important;
border-radius: 14px !important;
overflow: hidden !important;
padding: 0 !important;
}
details > summary,
.gr-examples summary,
div[class*="examples"] summary {
color: #c8933a !important;
font-family: 'Outfit', sans-serif !important;
font-size: 0.82rem !important; font-weight: 600 !important;
letter-spacing: 0.06em !important; padding: 10px 16px !important;
background: rgba(200,150,60,0.06) !important;
border-bottom: 1px solid rgba(200,150,60,0.12) !important;
cursor: pointer !important; list-style: none !important;
}
table, .gr-samples-table, div[class*="examples"] table {
background: #131008 !important;
border-collapse: collapse !important; width: 100% !important;
}
thead, thead tr,
.gr-samples-table thead, .gr-samples-table thead tr {
background: #1e1a10 !important;
}
thead th, .gr-samples-table thead th, div[class*="examples"] thead th {
color: #d4a650 !important;
font-family: 'Outfit', sans-serif !important;
font-size: 0.78rem !important; font-weight: 700 !important;
text-transform: uppercase !important; letter-spacing: 0.1em !important;
padding: 12px 16px !important;
border-bottom: 1px solid rgba(200,150,60,0.2) !important;
background: #1e1a10 !important;
}
tbody tr, .gr-samples-table tbody tr, div[class*="examples"] tbody tr {
background: #131008 !important;
border-bottom: 1px solid rgba(200,150,60,0.1) !important;
transition: background 0.15s !important; cursor: pointer !important;
}
tbody tr:nth-child(even), .gr-samples-table tbody tr:nth-child(even) {
background: #171209 !important;
}
tbody tr:hover, .gr-samples-table tbody tr:hover,
div[class*="examples"] tbody tr:hover {
background: #252010 !important;
}
tbody td, .gr-samples-table td,
div[class*="examples"] tbody td, div[class*="examples"] td {
color: #e0d4c0 !important;
font-family: 'Outfit', sans-serif !important;
font-size: 0.86rem !important; font-weight: 400 !important;
padding: 12px 16px !important; line-height: 1.4 !important;
vertical-align: middle !important;
}
tbody td p, tbody td span, tbody td * { color: #e0d4c0 !important; font-size: 0.86rem !important; }
tbody td button, div[class*="examples"] td button {
color: #e0d4c0 !important; background: transparent !important;
border: none !important; font-family: 'Outfit', sans-serif !important;
font-size: 0.86rem !important; text-align: left !important;
padding: 0 !important; cursor: pointer !important; width: 100% !important;
}
tbody td button:hover { color: #f0c860 !important; }
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: #0a0806; }
::-webkit-scrollbar-thumb { background: #4a3a28; border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: #c8933a; }
"""
# ── Build UI ──────────────────────────────────────────────────────────────────
with gr.Blocks(title="Dish Image Generator Β· FLUX.1-schnell", css=css) as demo:
# ── Hero ─────────────────────────────────────────────────────────────────
gr.HTML("""
<div class="hero">
<div class="hero-eyebrow">✦ &nbsp; AI Culinary Photography &nbsp; ✦</div>
<h1>🍽️ The <em>Plateur</em></h1>
<p class="hero-sub">
Powered by <strong style="color:#f0c060">FLUX.1-schnell</strong> via Hugging Face InferenceClient
&nbsp;Β·&nbsp; <strong style="color:#5fcf7a">Completely FREE</strong> &nbsp;Β·&nbsp; Apache 2.0 licensed
</p>
<div class="hero-badges">
<span class="badge green">✦ $0.00 Cost</span>
<span class="badge">⚑ FLUX.1-schnell</span>
<span class="badge">πŸ–ΌοΈ Up to 1344 px</span>
<span class="badge">πŸ”“ Apache 2.0</span>
<span class="badge">πŸš€ 1–4 steps</span>
</div>
</div>
""")
gr.HTML('<div style="height:8px"></div>')
# ── Two-column: Recipe Details | Generated Image ───────────────────────
with gr.Row(equal_height=False):
with gr.Column(scale=5, min_width=340):
with gr.Group(elem_classes="card"):
gr.HTML('<div class="card-title">πŸ₯• &nbsp; Recipe Details</div>')
ingredients_input = gr.Textbox(
label="Ingredients",
placeholder="Please list all the ingredients in your dish, separated by commas. For example: 'chicken, garlic, lemon, rosemary'.",
lines=3,
info="List everything in your dish, separated by commas. The more detail, the better.",
)
description_input = gr.Textbox(
label="Dish Description",
placeholder=(
"Describe your dish in vivid detail. For example: 'Herb-roasted chicken with crispy golden skin, surrounded by caramelized baby potatoes, drizzled with lemon-garlic butter, garnished with fresh rosemary sprigs in a cast-iron skillet.'"
),
lines=5,
info="Paint a vivid picture β€” describe the colors, textures, presentation, and atmosphere.",
)
with gr.Column(scale=5, min_width=340):
with gr.Group(elem_classes="card"):
gr.HTML('<div class="card-title">πŸ–ΌοΈ &nbsp; Generated Image</div>')
image_output = gr.Image(
label="", type="filepath",
elem_classes="img-out", height=400, show_label=False,
)
status_output = gr.Textbox(
label="", interactive=False,
elem_classes="status-out", lines=2, show_label=False,
)
gr.HTML('<div style="height:8px"></div>')
# ── Visual Style β€” full width ─────────────────────────────────────────────
with gr.Group(elem_classes="card"):
gr.HTML('<div class="card-title">🎨 &nbsp; Visual Style & Output Size</div>')
with gr.Row():
style_dropdown = gr.Dropdown(
label="Photography Style",
choices=list(STYLE_PRESETS.keys()),
value=list(STYLE_PRESETS.keys())[0],
info="Selects the lighting, surface, and mood of the final photo.",
)
size_dropdown = gr.Dropdown(
label="Output Size",
choices=list(SIZE_OPTIONS.keys()),
value=list(SIZE_OPTIONS.keys())[0],
info="Choose the canvas shape. Square works best for most food shots.",
)
gr.HTML('<div style="height:8px"></div>')
# ── Generation Settings β€” FULL WIDTH ─────────────────────────────────────
with gr.Group(elem_classes="card"):
gr.HTML('<div class="card-title">βš™οΈ &nbsp; Generation Settings</div>')
with gr.Row(equal_height=True):
with gr.Column(scale=1):
steps_slider = gr.Slider(
label="Inference Steps",
minimum=1, maximum=8, step=1, value=4,
)
gr.HTML("""
<div class="setting-explainer">
<div class="label">πŸ’‘ What is Inference Steps?</div>
<div class="text">
Think of this as <strong>how many refinement passes</strong> the AI takes to draw your image.
More steps = more detail and quality, but takes longer to generate.<br><br>
<em>FLUX.1-schnell</em> is a <strong>speed-optimised "distilled" model</strong>, so it produces
excellent results in just <strong>1–4 steps</strong> β€” far fewer than standard models
that need 20–50 steps. Going above 4 rarely improves quality here.<br><br>
<strong>Recommended:</strong> 4 steps for best quality &nbsp;Β·&nbsp; 1–2 steps for fastest preview.
</div>
</div>
""")
with gr.Column(scale=1):
guidance_slider = gr.Slider(
label="Guidance Scale",
minimum=0.0, maximum=5.0, step=0.1, value=0.0,
)
gr.HTML("""
<div class="setting-explainer">
<div class="label">πŸ’‘ What is Guidance Scale?</div>
<div class="text">
This controls <strong>how strictly the AI follows your text prompt</strong>.
A higher value makes the image stick closer to your description but can look
over-saturated or unnatural. A lower value gives the AI more creative freedom.<br><br>
For <em>FLUX.1-schnell</em>, this should <strong>always stay at 0.0</strong>.
Schnell is a distilled model trained without classifier-free guidance, so turning
it up actually <strong>hurts</strong> image quality rather than helping it.<br><br>
<strong>Recommended:</strong> Keep at 0.0 for FLUX.1-schnell. Always.
</div>
</div>
""")
gr.HTML('<div style="height:8px"></div>')
for style_name, prompt in STYLE_PRESETS.items():
with gr.Accordion(style_name, open=False):
gr.Textbox(
value=prompt,
label="Prompt",
lines=2,
interactive=False
)
# ── Generate button β€” full width ──────────────────────────────────────────
generate_btn = gr.Button("✨ Generate Dish Image", elem_classes="gen-btn")
gr.HTML('<div style="height:8px"></div>')
# ── Info bar ──────────────────────────────────────────────────────────────
gr.HTML("""
<div class="info-bar">
<div class="info-item free">βœ… <strong>Cost</strong> β€” $0.00 free forever</div>
<div class="info-item">⚑ <strong>Model</strong> β€” black-forest-labs/FLUX.1-schnell</div>
<div class="info-item">πŸ”— <strong>Client</strong> β€” huggingface_hub InferenceClient</div>
<div class="info-item">πŸ“ <strong>Sizes</strong> β€” 768–1344 px Β· 5 aspect ratios</div>
<div class="info-item">πŸ”“ <strong>License</strong> β€” Apache 2.0</div>
</div>
""")
gr.HTML('<div style="height:14px"></div>')
# ── Examples ──────────────────────────────────────────────────────────────
gr.HTML('<div class="examples-label">✦ &nbsp; Example Recipes β€” click any row to load</div>')
gr.Examples(
examples=EXAMPLES,
inputs=[ingredients_input, description_input, style_dropdown, size_dropdown],
label="",
examples_per_page=5,
)
# ── Wire up ───────────────────────────────────────────────────────────────
generate_btn.click(
fn=generate_dish_image,
inputs=[
ingredients_input, description_input,
style_dropdown, size_dropdown, steps_slider, guidance_slider,
],
outputs=[image_output, status_output],
)
if __name__ == "__main__":
demo.launch()