| import gradio as gr |
| import tempfile |
| import time |
| import os |
| from huggingface_hub import InferenceClient |
|
|
| |
| FLUX_MODEL = "black-forest-labs/FLUX.1-schnell" |
|
|
| |
| 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 = [ |
|
|
| |
| [ |
| "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)", |
| ], |
|
|
| |
| [ |
| "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)", |
| ], |
|
|
| |
| [ |
| "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)", |
| ], |
|
|
| |
| [ |
| "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)", |
| ], |
|
|
| |
| [ |
| "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)", |
| ], |
|
|
| |
| [ |
| "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)", |
| ], |
|
|
| |
| [ |
| "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)", |
| ], |
|
|
| |
| [ |
| "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)", |
| ], |
| ] |
|
|
| |
| 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 = """ |
| @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; } |
| """ |
|
|
|
|
| |
| with gr.Blocks(title="Dish Image Generator Β· FLUX.1-schnell", css=css) as demo: |
|
|
| |
| gr.HTML(""" |
| <div class="hero"> |
| <div class="hero-eyebrow">β¦ AI Culinary Photography β¦</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 |
| Β· <strong style="color:#5fcf7a">Completely FREE</strong> Β· 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>') |
|
|
| |
| 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">π₯ 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">πΌοΈ 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>') |
|
|
| |
| with gr.Group(elem_classes="card"): |
| gr.HTML('<div class="card-title">π¨ 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>') |
|
|
| |
| with gr.Group(elem_classes="card"): |
| gr.HTML('<div class="card-title">βοΈ 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 Β· 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_btn = gr.Button("β¨ Generate Dish Image", elem_classes="gen-btn") |
|
|
| gr.HTML('<div style="height:8px"></div>') |
|
|
| |
| 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>') |
|
|
| |
| gr.HTML('<div class="examples-label">β¦ 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, |
| ) |
|
|
| |
| 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() |