Add Quick Start scenario pickers (Norma Jean + Marilyn) with per-scenario input-requirement hints
Browse files
app.py
CHANGED
|
@@ -342,25 +342,117 @@ css = """
|
|
| 342 |
}
|
| 343 |
"""
|
| 344 |
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
|
| 365 |
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
|
| 366 |
gallery_state = gr.State([])
|
|
@@ -399,6 +491,17 @@ FLUX.1-dev has weaker safety training, so the NSFW LoRAs actually work here. Bes
|
|
| 399 |
type="pil",
|
| 400 |
height=300,
|
| 401 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
id_prompt = gr.Textbox(
|
| 403 |
label="Variation Prompt",
|
| 404 |
placeholder="Same person, shirtless, different angle...",
|
|
@@ -408,15 +511,6 @@ FLUX.1-dev has weaker safety training, so the NSFW LoRAs actually work here. Bes
|
|
| 408 |
id_gen_btn = gr.Button("Generate", variant="primary", scale=2)
|
| 409 |
id_gen_add_btn = gr.Button("Generate & Add", variant="secondary", scale=2)
|
| 410 |
|
| 411 |
-
with gr.Accordion("Presets", open=True):
|
| 412 |
-
for i in range(0, len(IDENTITY_PRESETS), 2):
|
| 413 |
-
with gr.Row():
|
| 414 |
-
for j in range(2):
|
| 415 |
-
if i + j < len(IDENTITY_PRESETS):
|
| 416 |
-
label, text = IDENTITY_PRESETS[i + j]
|
| 417 |
-
btn = gr.Button(label, size="sm", min_width=120)
|
| 418 |
-
btn.click(fn=make_preset_fn(text), outputs=[id_prompt])
|
| 419 |
-
|
| 420 |
id_candid = gr.Checkbox(
|
| 421 |
label="Candid realism mode (kills the AI gloss)",
|
| 422 |
value=True,
|
|
@@ -472,6 +566,16 @@ FLUX.1-dev has weaker safety training, so the NSFW LoRAs actually work here. Bes
|
|
| 472 |
lines=1,
|
| 473 |
info="Gets prepended to every prompt. The more specific, the more the output looks like your guy.",
|
| 474 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
ex_prompt = gr.Textbox(
|
| 476 |
label="Explicit Prompt",
|
| 477 |
placeholder="same person, fully nude, anatomically correct...",
|
|
@@ -481,15 +585,6 @@ FLUX.1-dev has weaker safety training, so the NSFW LoRAs actually work here. Bes
|
|
| 481 |
ex_gen_btn = gr.Button("Generate", variant="primary", scale=2)
|
| 482 |
ex_gen_add_btn = gr.Button("Generate & Add", variant="secondary", scale=2)
|
| 483 |
|
| 484 |
-
with gr.Accordion("Presets", open=True):
|
| 485 |
-
for i in range(0, len(EXPLICIT_PRESETS), 2):
|
| 486 |
-
with gr.Row():
|
| 487 |
-
for j in range(2):
|
| 488 |
-
if i + j < len(EXPLICIT_PRESETS):
|
| 489 |
-
label, text = EXPLICIT_PRESETS[i + j]
|
| 490 |
-
btn = gr.Button(label, size="sm", min_width=120)
|
| 491 |
-
btn.click(fn=make_preset_fn(text), outputs=[ex_prompt])
|
| 492 |
-
|
| 493 |
with gr.Accordion("Advanced", open=False):
|
| 494 |
ex_seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
|
| 495 |
ex_randomize = gr.Checkbox(label="Randomize seed", value=True)
|
|
@@ -551,6 +646,18 @@ FLUX.1-dev has weaker safety training, so the NSFW LoRAs actually work here. Bes
|
|
| 551 |
"Train your character LoRAs here, animate them there.</p>"
|
| 552 |
)
|
| 553 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
# --- Identity tab events ---
|
| 555 |
id_gen_btn.click(
|
| 556 |
fn=generate_identity,
|
|
|
|
| 342 |
}
|
| 343 |
"""
|
| 344 |
|
| 345 |
+
# ============ Quick Start Scenarios (one-click: sets prompt + every setting + tells
|
| 346 |
+
# you what the input should be) ============
|
| 347 |
+
# Identity (Norma Jean / Kontext) scenarios. Kontext re-poses from a reference, so the
|
| 348 |
+
# input hint is about what reference photo gives the cleanest result.
|
| 349 |
+
IDENTITY_SCENARIOS = {
|
| 350 |
+
"Face close-up (studio)": {
|
| 351 |
+
"prompt": "Same person, face close-up portrait, sharp focus, looking toward camera, even studio lighting, clean solid background",
|
| 352 |
+
"candid": False, "nsfw": 0.3, "realism": 1.0, "guidance": 2.5, "steps": 28,
|
| 353 |
+
"hint": "a clear, well-lit photo where the FACE is visible. Front-on or a slight angle is best.",
|
| 354 |
+
},
|
| 355 |
+
"3/4 angle portrait": {
|
| 356 |
+
"prompt": "Same person, three-quarter angle view, upper body, soft directional light from the left, subtle natural expression",
|
| 357 |
+
"candid": False, "nsfw": 0.3, "realism": 1.0, "guidance": 2.5, "steps": 28,
|
| 358 |
+
"hint": "any clear face photo. Head-and-shoulders works great.",
|
| 359 |
+
},
|
| 360 |
+
"Full body (training staple)": {
|
| 361 |
+
"prompt": "Same person, full body standing, neutral relaxed pose, even studio lighting, solid gray background",
|
| 362 |
+
"candid": False, "nsfw": 0.4, "realism": 1.0, "guidance": 2.5, "steps": 28,
|
| 363 |
+
"hint": "a photo that shows his build if you have one. Face-only still works — the body is inferred.",
|
| 364 |
+
},
|
| 365 |
+
"Shirtless torso": {
|
| 366 |
+
"prompt": "Same person, shirtless, upper body and torso, warm studio light, relaxed confident expression, simple background",
|
| 367 |
+
"candid": False, "nsfw": 1.0, "realism": 1.0, "guidance": 2.5, "steps": 28,
|
| 368 |
+
"hint": "face + upper body if possible. Kontext keeps the face and generates the torso.",
|
| 369 |
+
},
|
| 370 |
+
"Candid mirror selfie": {
|
| 371 |
+
"prompt": "Same person, casual mirror selfie, phone in hand, bedroom or bathroom, natural window light, authentic",
|
| 372 |
+
"candid": True, "nsfw": 0.5, "realism": 1.0, "guidance": 2.5, "steps": 28,
|
| 373 |
+
"hint": "any clear face photo. This one leans hardest on candid realism.",
|
| 374 |
+
},
|
| 375 |
+
"Golden hour outdoor": {
|
| 376 |
+
"prompt": "Same person, outdoors at golden hour, warm low sunlight, half body, relaxed candid feel",
|
| 377 |
+
"candid": True, "nsfw": 0.4, "realism": 1.0, "guidance": 2.5, "steps": 28,
|
| 378 |
+
"hint": "a clear face photo — the lighting gets replaced with golden hour.",
|
| 379 |
+
},
|
| 380 |
+
"Dramatic cinematic": {
|
| 381 |
+
"prompt": "Same person, dramatic side lighting, moody shadows, face and torso, high contrast, cinematic",
|
| 382 |
+
"candid": False, "nsfw": 0.5, "realism": 1.0, "guidance": 3.0, "steps": 30,
|
| 383 |
+
"hint": "a sharp, well-exposed face photo. Dramatic lighting needs clean features to work from.",
|
| 384 |
+
},
|
| 385 |
+
"Over-the-shoulder / back": {
|
| 386 |
+
"prompt": "Same person seen from behind, looking back over the shoulder, upper body and face visible, natural light",
|
| 387 |
+
"candid": False, "nsfw": 0.6, "realism": 1.0, "guidance": 2.5, "steps": 28,
|
| 388 |
+
"hint": "a front-facing face photo is fine — the model turns the pose for you.",
|
| 389 |
+
},
|
| 390 |
+
}
|
| 391 |
|
| 392 |
+
# Explicit (Marilyn / FLUX.1-dev img2img) scenarios. This is img2img: it KEEPS the
|
| 393 |
+
# composition of the starting image, so the input hint really matters here.
|
| 394 |
+
EXPLICIT_SCENARIOS = {
|
| 395 |
+
"Full nude — standing": {
|
| 396 |
+
"prompt": "same person, fully nude, full body standing, anatomically correct male, penis visible, studio lighting, solid neutral background, realistic, high quality",
|
| 397 |
+
"strength": 0.80, "guidance": 3.5, "steps": 28, "nsfw": 1.0, "realism": 1.0, "candid": False,
|
| 398 |
+
"hint": "a FULL-BODY STANDING shot. Img2img keeps the pose — start standing to get a standing nude.",
|
| 399 |
+
},
|
| 400 |
+
"Nude — reclining / lying down": {
|
| 401 |
+
"prompt": "same person, nude, reclining on bed, relaxed, full body visible, anatomically detailed male, warm soft bedroom light, intimate, realistic",
|
| 402 |
+
"strength": 0.80, "guidance": 3.5, "steps": 28, "nsfw": 1.0, "realism": 1.0, "candid": False,
|
| 403 |
+
"hint": "a shot where he is ALREADY LYING DOWN or reclining. Img2img won't cleanly turn a standing photo into a lying-down one.",
|
| 404 |
+
},
|
| 405 |
+
"Shower / wet": {
|
| 406 |
+
"prompt": "same person, nude, wet skin, standing in shower, water droplets, steam, full body, anatomically correct, natural lighting, realistic",
|
| 407 |
+
"strength": 0.80, "guidance": 3.5, "steps": 28, "nsfw": 1.0, "realism": 1.0, "candid": False,
|
| 408 |
+
"hint": "a STANDING full-body shot.",
|
| 409 |
+
},
|
| 410 |
+
"Nude from behind": {
|
| 411 |
+
"prompt": "same person, nude from behind, athletic back and buttocks visible, looking over shoulder, studio lighting, realistic",
|
| 412 |
+
"strength": 0.80, "guidance": 3.5, "steps": 28, "nsfw": 1.0, "realism": 1.0, "candid": False,
|
| 413 |
+
"hint": "a standing shot; a back or 3/4 view works best.",
|
| 414 |
+
},
|
| 415 |
+
"Keep-the-face light nude": {
|
| 416 |
+
"prompt": "same person, nude, natural relaxed pose, anatomically correct male, soft lighting, realistic",
|
| 417 |
+
"strength": 0.60, "guidance": 3.5, "steps": 28, "nsfw": 1.0, "realism": 1.0, "candid": False,
|
| 418 |
+
"hint": "your BEST identity shot. Low strength (0.60) preserves the face — also fill in the Character Description box.",
|
| 419 |
+
},
|
| 420 |
+
"Explicit — max freedom": {
|
| 421 |
+
"prompt": "same person, nude, explicit, anatomically detailed male, realistic, high quality",
|
| 422 |
+
"strength": 0.85, "guidance": 3.5, "steps": 28, "nsfw": 1.0, "realism": 1.0, "candid": False,
|
| 423 |
+
"hint": "a full-body shot. High strength (0.85) changes the body most — fill in the Character Description box to hold the face.",
|
| 424 |
+
},
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def apply_identity_scenario(name):
|
| 429 |
+
s = IDENTITY_SCENARIOS.get(name)
|
| 430 |
+
if not s:
|
| 431 |
+
return (gr.update(),) * 7
|
| 432 |
+
return (
|
| 433 |
+
gr.update(value=s["prompt"]),
|
| 434 |
+
gr.update(value=s["candid"]),
|
| 435 |
+
gr.update(value=s["nsfw"]),
|
| 436 |
+
gr.update(value=s["realism"]),
|
| 437 |
+
gr.update(value=s["guidance"]),
|
| 438 |
+
gr.update(value=s["steps"]),
|
| 439 |
+
gr.update(value=f"**Best input for this scenario:** {s['hint']}"),
|
| 440 |
+
)
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
def apply_explicit_scenario(name):
|
| 444 |
+
s = EXPLICIT_SCENARIOS.get(name)
|
| 445 |
+
if not s:
|
| 446 |
+
return (gr.update(),) * 7
|
| 447 |
+
return (
|
| 448 |
+
gr.update(value=s["prompt"]),
|
| 449 |
+
gr.update(value=s["strength"]),
|
| 450 |
+
gr.update(value=s["nsfw"]),
|
| 451 |
+
gr.update(value=s["realism"]),
|
| 452 |
+
gr.update(value=s["guidance"]),
|
| 453 |
+
gr.update(value=s["steps"]),
|
| 454 |
+
gr.update(value=f"**Best input for this scenario:** {s['hint']}"),
|
| 455 |
+
)
|
| 456 |
|
| 457 |
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
|
| 458 |
gallery_state = gr.State([])
|
|
|
|
| 491 |
type="pil",
|
| 492 |
height=300,
|
| 493 |
)
|
| 494 |
+
|
| 495 |
+
with gr.Group():
|
| 496 |
+
gr.Markdown("**🚀 Quick Start — pick a scenario, everything gets set up for you**")
|
| 497 |
+
with gr.Row():
|
| 498 |
+
id_scenario_dd = gr.Dropdown(
|
| 499 |
+
choices=list(IDENTITY_SCENARIOS.keys()),
|
| 500 |
+
label="Scenario", value=None, scale=3,
|
| 501 |
+
)
|
| 502 |
+
id_scenario_btn = gr.Button("Apply", variant="secondary", scale=1)
|
| 503 |
+
id_scenario_hint = gr.Markdown("")
|
| 504 |
+
|
| 505 |
id_prompt = gr.Textbox(
|
| 506 |
label="Variation Prompt",
|
| 507 |
placeholder="Same person, shirtless, different angle...",
|
|
|
|
| 511 |
id_gen_btn = gr.Button("Generate", variant="primary", scale=2)
|
| 512 |
id_gen_add_btn = gr.Button("Generate & Add", variant="secondary", scale=2)
|
| 513 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 514 |
id_candid = gr.Checkbox(
|
| 515 |
label="Candid realism mode (kills the AI gloss)",
|
| 516 |
value=True,
|
|
|
|
| 566 |
lines=1,
|
| 567 |
info="Gets prepended to every prompt. The more specific, the more the output looks like your guy.",
|
| 568 |
)
|
| 569 |
+
with gr.Group():
|
| 570 |
+
gr.Markdown("**🚀 Quick Start — pick a scenario, everything gets set up for you**")
|
| 571 |
+
with gr.Row():
|
| 572 |
+
ex_scenario_dd = gr.Dropdown(
|
| 573 |
+
choices=list(EXPLICIT_SCENARIOS.keys()),
|
| 574 |
+
label="Scenario", value=None, scale=3,
|
| 575 |
+
)
|
| 576 |
+
ex_scenario_btn = gr.Button("Apply", variant="secondary", scale=1)
|
| 577 |
+
ex_scenario_hint = gr.Markdown("")
|
| 578 |
+
|
| 579 |
ex_prompt = gr.Textbox(
|
| 580 |
label="Explicit Prompt",
|
| 581 |
placeholder="same person, fully nude, anatomically correct...",
|
|
|
|
| 585 |
ex_gen_btn = gr.Button("Generate", variant="primary", scale=2)
|
| 586 |
ex_gen_add_btn = gr.Button("Generate & Add", variant="secondary", scale=2)
|
| 587 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 588 |
with gr.Accordion("Advanced", open=False):
|
| 589 |
ex_seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
|
| 590 |
ex_randomize = gr.Checkbox(label="Randomize seed", value=True)
|
|
|
|
| 646 |
"Train your character LoRAs here, animate them there.</p>"
|
| 647 |
)
|
| 648 |
|
| 649 |
+
# --- Quick Start scenario events ---
|
| 650 |
+
id_scenario_btn.click(
|
| 651 |
+
fn=apply_identity_scenario,
|
| 652 |
+
inputs=[id_scenario_dd],
|
| 653 |
+
outputs=[id_prompt, id_candid, id_nsfw, id_realism, id_guidance, id_steps, id_scenario_hint],
|
| 654 |
+
)
|
| 655 |
+
ex_scenario_btn.click(
|
| 656 |
+
fn=apply_explicit_scenario,
|
| 657 |
+
inputs=[ex_scenario_dd],
|
| 658 |
+
outputs=[ex_prompt, ex_strength, ex_nsfw, ex_realism, ex_guidance, ex_steps, ex_scenario_hint],
|
| 659 |
+
)
|
| 660 |
+
|
| 661 |
# --- Identity tab events ---
|
| 662 |
id_gen_btn.click(
|
| 663 |
fn=generate_identity,
|