Spaces:
Running on Zero
Running on Zero
Commit ·
ec440e5
1
Parent(s): f7097b9
Split into 2 tabs: Tab1 Identity (close-up face builder + send-to-scene as FaceID), Tab2 = existing scene builder (#39)
Browse files- Split into 2 tabs: Tab1 Identity (close-up face builder + send-to-scene as FaceID), Tab2 = existing scene builder (899f185fd8e1bd5177663d04e88316021b2ad93f)
Co-authored-by: pormungtailaw <pormungtai@users.noreply.huggingface.co>
app.py
CHANGED
|
@@ -219,6 +219,63 @@ def build_prompt(subject, age, ethnicity, skin, face, body, hair, eyes, outfit,
|
|
| 219 |
# ---------------------------------------------------------------------------
|
| 220 |
# Layout (mirrors the FLUX LoRA DLC reference UI)
|
| 221 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
CSS = """
|
| 223 |
#gen-btn {height: 100%; font-size: 1.3rem; font-weight: 700;}
|
| 224 |
.card {border-radius: 14px;}
|
|
@@ -228,157 +285,188 @@ footer {visibility: hidden;}
|
|
| 228 |
with gr.Blocks(css=CSS, theme=gr.themes.Soft(primary_hue="blue"),
|
| 229 |
title="Character Studio") as demo:
|
| 230 |
gr.Markdown("## 🎭 Character Studio — multi-model character generator (ZeroGPU)")
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
label="
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
("
|
| 255 |
-
|
| 256 |
-
("
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
b_hair = gr.Textbox(label="ทรงผม / สีผม", placeholder="เช่น ผมยาวสีดำ, ผมบลอนด์")
|
| 268 |
-
b_eyes = gr.Textbox(label="สีตา", placeholder="เช่น ตาสีน้ำตาล")
|
| 269 |
-
with gr.Row():
|
| 270 |
-
b_skin = gr.Textbox(label="สีผิว", placeholder="เช่น ผิวขาว, ผิวสองสี, ผิวแทนทอง, ผิวเนียน")
|
| 271 |
-
b_face = gr.Textbox(label="โครงหน้า", placeholder="เช่น หน้าเรียว, หน้ารูปไข่, หน้ากลม, คางแหลม")
|
| 272 |
-
with gr.Row():
|
| 273 |
-
b_outfit = gr.Textbox(label="ชุด / เสื้อผ้า", placeholder="เช่น เดรสสีขาว, ชุดนักเรียน")
|
| 274 |
-
b_pose = gr.Dropdown(
|
| 275 |
-
label="ท่าโพส (เลือกผสมได้ · พิมพ์เองได้)",
|
| 276 |
-
choices=[
|
| 277 |
-
("ยืนตรง / standing", "standing"),
|
| 278 |
-
("ยืนมือเท้าเอว / hands on hips", "standing, hands on hips"),
|
| 279 |
-
("ยืนเอียงสะโพก / contrapposto", "standing, contrapposto pose"),
|
| 280 |
-
("ยืนพิงกำแพง / leaning on wall", "leaning against wall"),
|
| 281 |
-
("นั่งเก้าอี้ / sitting (chair)", "sitting on chair"),
|
| 282 |
-
("นั่งพื้น / sitting (floor)", "sitting on floor"),
|
| 283 |
-
("นั่งไขว่ห้าง / crossed legs", "sitting, crossed legs"),
|
| 284 |
-
("นั่งชันเข่า / knees up", "sitting, knees up, hugging knees"),
|
| 285 |
-
("คุกเข่า / kneeling", "kneeling"),
|
| 286 |
-
("นอนหงาย / lying on back", "lying on back"),
|
| 287 |
-
("นอนคว่ำ / on stomach", "lying on stomach"),
|
| 288 |
-
("นอนตะแคง / on side", "lying on side"),
|
| 289 |
-
("เดิน / walking", "walking"),
|
| 290 |
-
("วิ่ง / running", "running"),
|
| 291 |
-
("มือรวบผม / hand in hair", "hand in own hair, arm up"),
|
| 292 |
-
("กอดอก / arms crossed", "crossed arms"),
|
| 293 |
-
("โบกมือ / waving", "waving hand"),
|
| 294 |
-
],
|
| 295 |
-
value=None, multiselect=True, allow_custom_value=True)
|
| 296 |
-
b_expr = gr.Textbox(label="สีหน้า / อารมณ์", placeholder="เช่น ยิ้มอ่อนๆ")
|
| 297 |
-
with gr.Row():
|
| 298 |
-
b_scene = gr.Textbox(label="สถานที่ / ฉาก", placeholder="เช่น คาเฟ่, ริมทะเล, ในสวน")
|
| 299 |
-
b_light = gr.Textbox(label="แสง", placeholder="เช่น แสงเช้านุ่มๆ, แสงสตูดิโอ")
|
| 300 |
-
b_shot = gr.Dropdown(
|
| 301 |
-
label="มุมกล้อง / ช็อต (เลือกได้หลายอัน · พิมพ์เองได้)",
|
| 302 |
-
choices=[
|
| 303 |
-
("โคลสอัพใบหน้า / close-up", "close-up portrait, face focus, headshot"),
|
| 304 |
-
("ครึ่งตัวบน / upper body", "upper body shot, bust"),
|
| 305 |
-
("ครึ่งตัว เอวขึ้นไป / half body", "half body shot, waist up"),
|
| 306 |
-
("เต็มตัว หัวถึงเท้า / full body", "full body shot, head to toe, full body visible"),
|
| 307 |
-
("ระยะไกล เห็นรอบตัว / wide shot", "wide shot, full body, distant, environmental"),
|
| 308 |
-
("มุมเงย / low angle", "from below, low angle shot"),
|
| 309 |
-
("มุมก้ม / high angle", "from above, high angle shot"),
|
| 310 |
-
("มุมข้าง โปรไฟล์ / side", "from side, profile view"),
|
| 311 |
-
("มุมหลัง / from behind", "from behind, back view"),
|
| 312 |
-
("เซลฟี่ / selfie", "selfie, pov, arm extended"),
|
| 313 |
-
],
|
| 314 |
-
value=None, multiselect=True, allow_custom_value=True)
|
| 315 |
-
with gr.Row():
|
| 316 |
-
b_pose_img = gr.Image(
|
| 317 |
-
label="📷 รูปอ้างอิงท่าโพส (ใส่รูป → ล็อกท่าตามรูปด้วย ControlNet OpenPose · "
|
| 318 |
-
"หน้า/ชุด/ฉาก ยังคุมด้วยช่องด้านบน · เว้นว่าง = ใช้ข้อความท่าโพส)",
|
| 319 |
-
type="pil", sources=["upload"], height=240)
|
| 320 |
-
build_btn = gr.Button("✨ สร้าง prompt → ใส่ในช่องด้านบน", variant="secondary")
|
| 321 |
-
|
| 322 |
-
# State: ordered list of model ids (matches gallery order) + current selection.
|
| 323 |
-
ids_state = gr.State(model_ids(MODELS))
|
| 324 |
-
selected_id = gr.State(MODELS[0]["id"] if MODELS else None)
|
| 325 |
-
|
| 326 |
-
with gr.Row(equal_height=False):
|
| 327 |
-
# ---- left: model picker ----
|
| 328 |
-
with gr.Column(scale=1):
|
| 329 |
-
with gr.Group():
|
| 330 |
-
gr.Markdown("### 🧩 เลือกโมเดล / Models")
|
| 331 |
-
model_gallery = gr.Gallery(
|
| 332 |
-
value=gallery_items(MODELS),
|
| 333 |
-
label=None, show_label=False, columns=2, height="auto",
|
| 334 |
-
object_fit="cover", allow_preview=False, container=False,
|
| 335 |
-
elem_classes="card",
|
| 336 |
)
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
denoise = gr.Slider(0.1, 1.0, value=0.65, step=0.01,
|
| 370 |
-
label="Denoise strength (img2img · ต่ำ = อิงรูปมาก)")
|
| 371 |
-
with gr.Column():
|
| 372 |
-
negative_prompt = gr.Textbox(label="Negative prompt", lines=2)
|
| 373 |
with gr.Row():
|
| 374 |
-
|
| 375 |
-
|
| 376 |
with gr.Row():
|
| 377 |
-
|
| 378 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
with gr.Row():
|
| 380 |
-
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
# ---- wiring ----
|
| 384 |
model_gallery.select(
|
|
@@ -404,6 +492,26 @@ with gr.Blocks(css=CSS, theme=gr.themes.Soft(primary_hue="blue"),
|
|
| 404 |
prompt.submit(generate, inputs=gen_inputs, outputs=[output, seed, status])
|
| 405 |
|
| 406 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
if __name__ == "__main__":
|
| 408 |
# allowed_paths lets Gradio serve the local model preview thumbnails.
|
| 409 |
demo.queue(max_size=12).launch(allowed_paths=["previews"])
|
|
|
|
| 219 |
# ---------------------------------------------------------------------------
|
| 220 |
# Layout (mirrors the FLUX LoRA DLC reference UI)
|
| 221 |
# ---------------------------------------------------------------------------
|
| 222 |
+
def build_face_prompt(gender, age, ethnicity, faceshape, skin, eyecolor,
|
| 223 |
+
eyes, brow, nose, mouth, hair, expr):
|
| 224 |
+
"""Assemble Tab-1 face fields into an identity-focused prompt (Thai ok)."""
|
| 225 |
+
who = _field(gender) or "ผู้หญิง"
|
| 226 |
+
e, a = _field(ethnicity), _field(age)
|
| 227 |
+
if e:
|
| 228 |
+
who = f"{e} {who}"
|
| 229 |
+
if a:
|
| 230 |
+
who = f"{who} อายุ {a} ปี"
|
| 231 |
+
parts = [who]
|
| 232 |
+
for v in (faceshape, skin, eyecolor, eyes, brow, nose, mouth, hair, expr):
|
| 233 |
+
s = _field(v)
|
| 234 |
+
if s:
|
| 235 |
+
parts.append(s)
|
| 236 |
+
return ", ".join(parts) + ", detailed face, beautiful detailed eyes, detailed skin"
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
@spaces.GPU(duration=120)
|
| 240 |
+
def generate_face(model_id, prompt, translator):
|
| 241 |
+
"""Tab 1: generate a close-up identity portrait to lock the face first."""
|
| 242 |
+
models = load_models()
|
| 243 |
+
cfg = pm.get_model(models, model_id)
|
| 244 |
+
if cfg is None:
|
| 245 |
+
raise gr.Error("ไม่พบโมเดล — กด Reload models ในแถบ 2 / model not found")
|
| 246 |
+
if not (prompt and str(prompt).strip()):
|
| 247 |
+
raise gr.Error("กรอกช่องใบหน้าแล้วกด '✨ สร้าง prompt ใบหน้า' ก่อน")
|
| 248 |
+
import random as _r
|
| 249 |
+
seed = _r.randint(0, MAX_SEED)
|
| 250 |
+
p = pm.translate_prompt(prompt, translator)
|
| 251 |
+
# force a close-up identity framing regardless of translator wording
|
| 252 |
+
p = "RAW photo, close-up portrait, face focus, headshot, looking at viewer, " + p
|
| 253 |
+
neg = cfg.get("negative_prompt", "")
|
| 254 |
+
try:
|
| 255 |
+
img = pm.run_generation(
|
| 256 |
+
cfg=cfg, mode="txt2img", prompt=p, negative_prompt=neg, ref_image=None,
|
| 257 |
+
steps=int(cfg.get("default_steps", 30)),
|
| 258 |
+
guidance=float(cfg.get("default_guidance", 5.0)),
|
| 259 |
+
denoise=0.4, ip_scale=0.7, width=512, height=640, seed=seed,
|
| 260 |
+
)
|
| 261 |
+
except Exception as e: # noqa
|
| 262 |
+
traceback.print_exc()
|
| 263 |
+
raise gr.Error(str(e))
|
| 264 |
+
return img, f"✅ ใบหน้า · {cfg['label']} · seed {seed}"
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def send_face_to_scene(face_img):
|
| 268 |
+
"""Copy the Tab-1 face into Tab-2's reference image + switch to FaceID."""
|
| 269 |
+
if face_img is None:
|
| 270 |
+
return gr.update(), gr.update(), "⚠️ ยังไม่มีใบหน้า — กด 'สร้างใบหน้า' ก่อน"
|
| 271 |
+
return (
|
| 272 |
+
gr.update(value=face_img),
|
| 273 |
+
gr.update(value="face_id"),
|
| 274 |
+
"✅ ส่งใบหน้าไปแถบ 2 แล้ว (ตั้งเป็น FaceID) — ไปแถบ '🎬 ฉาก/ท่าทาง' ตั้งค่าฉาก/ชุด/ท่า แล้วกด Generate",
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
|
| 279 |
CSS = """
|
| 280 |
#gen-btn {height: 100%; font-size: 1.3rem; font-weight: 700;}
|
| 281 |
.card {border-radius: 14px;}
|
|
|
|
| 285 |
with gr.Blocks(css=CSS, theme=gr.themes.Soft(primary_hue="blue"),
|
| 286 |
title="Character Studio") as demo:
|
| 287 |
gr.Markdown("## 🎭 Character Studio — multi-model character generator (ZeroGPU)")
|
| 288 |
+
with gr.Tabs():
|
| 289 |
+
with gr.Tab("👤 แถบ 1 · สร้างใบหน้า / Identity"):
|
| 290 |
+
gr.Markdown("### 👤 แถบ 1 · สร้างใบหน้า/อัตลักษณ์ก่อน — เน้นโคลสอัพให้ได้หน้าที่ต้องการ แล้วส่งไปแถบ 2")
|
| 291 |
+
gr.Markdown("กรอกลักษณะใบหน้า (ไทยได้) → กด สร้าง prompt → กด สร้างใบหน้า · ใช้โมเดลที่เลือกจากแถบ 2")
|
| 292 |
+
with gr.Row():
|
| 293 |
+
f_gender = gr.Textbox(label="เพศ", value="ผู้หญิง")
|
| 294 |
+
f_age = gr.Textbox(label="อายุ", placeholder="เช่น 22")
|
| 295 |
+
f_ethnicity = gr.Textbox(label="เชื้อชาติ", placeholder="เช่น ไทย, เกาหลี")
|
| 296 |
+
with gr.Row():
|
| 297 |
+
f_faceshape = gr.Textbox(label="โครงหน้า", placeholder="เช่น หน้าเรียว, รูปไข่, กลม")
|
| 298 |
+
f_skin = gr.Textbox(label="สีผิว", placeholder="เช่น ผิวขาว, สองสี, แทน")
|
| 299 |
+
f_eyecolor = gr.Textbox(label="สีตา", placeholder="เช่น น้ำตาล, ดำ")
|
| 300 |
+
with gr.Row():
|
| 301 |
+
f_eyes = gr.Textbox(label="ดวงตา", placeholder="เช่น ตากลมโต, ตาชั้นเดียว")
|
| 302 |
+
f_brow = gr.Textbox(label="คิ้ว", placeholder="เช่น คิ้วเข้ม, คิ้วโก่ง")
|
| 303 |
+
f_nose = gr.Textbox(label="จมูก", placeholder="เช่น จมูกโด่ง, เล็ก")
|
| 304 |
+
with gr.Row():
|
| 305 |
+
f_mouth = gr.Textbox(label="ปาก", placeholder="เช่น ปากอิ่ม, ยิ้มมุมปาก")
|
| 306 |
+
f_hair = gr.Textbox(label="ทรงผม / สีผม", placeholder="เช่น ผมยาวสีดำ")
|
| 307 |
+
f_expr = gr.Textbox(label="สีหน้า / อารมณ์", placeholder="เช่น ยิ้มอ่อนๆ, นิ่ง")
|
| 308 |
+
face_build_btn = gr.Button("✨ สร้าง prompt ใบหน้า", variant="secondary")
|
| 309 |
+
with gr.Row():
|
| 310 |
+
with gr.Column(scale=3):
|
| 311 |
+
face_prompt = gr.Textbox(label="Prompt ใบหน้า (แก้ได้)", lines=2, placeholder="กดปุ่มด้านบน หรือพิมพ์เอง")
|
| 312 |
+
with gr.Column(scale=1):
|
| 313 |
+
face_gen_btn = gr.Button("🎨 สร้างใบหน้า (close-up)", variant="primary")
|
| 314 |
+
face_output = gr.Image(label="ใบหน้าที่ได้", height=420, type="pil", elem_classes="card")
|
| 315 |
+
face_status = gr.Markdown("")
|
| 316 |
+
send_face_btn = gr.Button("➡️ ใช้ใบหน้านี้ในแถบ 2 (FaceID)", variant="secondary")
|
| 317 |
+
send_note = gr.Markdown("")
|
| 318 |
+
with gr.Tab("🎬 แถบ 2 · สร้างฉาก/ท่าทาง / Scene"):
|
| 319 |
+
|
| 320 |
+
with gr.Row():
|
| 321 |
+
prompt = gr.Textbox(
|
| 322 |
+
label="Edit Prompt", lines=2, scale=4,
|
| 323 |
+
placeholder="✦ เลือกโมเดลแล้วพิมพ์ prompt / Choose a model and type the prompt",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
)
|
| 325 |
+
gen_btn = gr.Button("Generate", variant="primary", scale=1, elem_id="gen-btn")
|
| 326 |
+
|
| 327 |
+
# ---- Prompt Builder: fill blanks instead of writing sentences ----
|
| 328 |
+
with gr.Accordion("📝 ตัวช่วยสร้าง prompt / Prompt Builder — กรอกช่อง ไม่ต้องเขียนประโยค",
|
| 329 |
+
open=False):
|
| 330 |
+
gr.Markdown("กรอกเฉพาะช่องที่ต้องการ (เป็นภาษาไทยได้) แล้วกดปุ่มด้านล่าง "
|
| 331 |
+
"ระบบจะประกอบเป็น prompt ให้ในช่องด้านบน · เว้นว่างช่องที่ไม่ใช้ได้")
|
| 332 |
+
with gr.Row():
|
| 333 |
+
b_subject = gr.Textbox(label="ใคร / ตัวละคร", value="ผู้หญิง")
|
| 334 |
+
b_age = gr.Textbox(label="อายุ", placeholder="เช่น 25")
|
| 335 |
+
b_ethnicity = gr.Textbox(label="เชื้อชาติ / สัญชาติ", placeholder="เช่น เกาหลี, ไทย")
|
| 336 |
+
with gr.Row():
|
| 337 |
+
b_body = gr.Dropdown(
|
| 338 |
+
label="รูปร่าง / สัดส่วน (เลือกได้หลายอัน · พิมพ์เองได้)",
|
| 339 |
+
choices=[
|
| 340 |
+
("ผอมเพรียว / slim", "slim, slender figure"),
|
| 341 |
+
("ตัวเล็กบอบบาง / petite", "petite, small frame"),
|
| 342 |
+
("ทรงนาฬิกาทราย / hourglass", "hourglass figure, narrow waist"),
|
| 343 |
+
("อวบอั๋นเว้าโค้ง / curvy", "curvy, voluptuous"),
|
| 344 |
+
("ฟิตมีกล้าม / athletic", "athletic, toned, fit body"),
|
| 345 |
+
("อึ๋มอกใหญ่ / busty", "busty, large breasts"),
|
| 346 |
+
("ท้วมอวบ / chubby", "chubby, plump"),
|
| 347 |
+
("สูงขายาวทรงนางแบบ / tall model", "tall, long legs, model body"),
|
| 348 |
+
("สะโพกผาย / wide hips", "wide hips"),
|
| 349 |
+
("ต้นขาหนา / thick thighs", "thick thighs"),
|
| 350 |
+
("เอวเล็ก / slim waist", "slim waist"),
|
| 351 |
+
("อกเล็ก / small breasts", "small breasts"),
|
| 352 |
+
("ทรงผู้ใหญ่ / mature", "mature body"),
|
| 353 |
+
],
|
| 354 |
+
value=None, multiselect=True, allow_custom_value=True)
|
| 355 |
+
b_hair = gr.Textbox(label="ทรงผม / สีผม", placeholder="เช่น ผม��าวสีดำ, ผมบลอนด์")
|
| 356 |
+
b_eyes = gr.Textbox(label="สีตา", placeholder="เช่น ตาสีน้ำตาล")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
with gr.Row():
|
| 358 |
+
b_skin = gr.Textbox(label="สีผิว", placeholder="เช่น ผิวขาว, ผิวสองสี, ผิวแทนทอง, ผิวเนียน")
|
| 359 |
+
b_face = gr.Textbox(label="โครงหน้า", placeholder="เช่น หน้าเรียว, หน้ารูปไข่, หน้ากลม, คางแหลม")
|
| 360 |
with gr.Row():
|
| 361 |
+
b_outfit = gr.Textbox(label="ชุด / เสื้อผ้า", placeholder="เช่น เดรสสีขาว, ชุดนักเรียน")
|
| 362 |
+
b_pose = gr.Dropdown(
|
| 363 |
+
label="ท่าโพส (เลือกผสมได้ · พิมพ์เองได้)",
|
| 364 |
+
choices=[
|
| 365 |
+
("ยืนตรง / standing", "standing"),
|
| 366 |
+
("ยืนมือเท้าเอว / hands on hips", "standing, hands on hips"),
|
| 367 |
+
("ยืนเอียงสะโพก / contrapposto", "standing, contrapposto pose"),
|
| 368 |
+
("ยืนพิงกำแพง / leaning on wall", "leaning against wall"),
|
| 369 |
+
("นั่งเก้าอี้ / sitting (chair)", "sitting on chair"),
|
| 370 |
+
("นั่งพื้น / sitting (floor)", "sitting on floor"),
|
| 371 |
+
("นั่งไขว่ห้าง / crossed legs", "sitting, crossed legs"),
|
| 372 |
+
("นั่งชันเข่า / knees up", "sitting, knees up, hugging knees"),
|
| 373 |
+
("คุกเข่า / kneeling", "kneeling"),
|
| 374 |
+
("นอนหงาย / lying on back", "lying on back"),
|
| 375 |
+
("นอนคว่ำ / on stomach", "lying on stomach"),
|
| 376 |
+
("นอนตะแคง / on side", "lying on side"),
|
| 377 |
+
("เดิน / walking", "walking"),
|
| 378 |
+
("วิ่ง / running", "running"),
|
| 379 |
+
("มือรวบผม / hand in hair", "hand in own hair, arm up"),
|
| 380 |
+
("กอดอก / arms crossed", "crossed arms"),
|
| 381 |
+
("โบกมือ / waving", "waving hand"),
|
| 382 |
+
],
|
| 383 |
+
value=None, multiselect=True, allow_custom_value=True)
|
| 384 |
+
b_expr = gr.Textbox(label="สีหน้า / อารมณ์", placeholder="เช่น ยิ้มอ่อนๆ")
|
| 385 |
with gr.Row():
|
| 386 |
+
b_scene = gr.Textbox(label="สถานที่ / ฉาก", placeholder="เช่น คาเฟ่, ริมทะเล, ในสวน")
|
| 387 |
+
b_light = gr.Textbox(label="แสง", placeholder="เช่น แสงเช้านุ่มๆ, แสงสตูดิโอ")
|
| 388 |
+
b_shot = gr.Dropdown(
|
| 389 |
+
label="มุมกล้อง / ช็อต (เลือกได้หลายอัน · พิมพ์เองได้)",
|
| 390 |
+
choices=[
|
| 391 |
+
("โคลสอัพใบหน้า / close-up", "close-up portrait, face focus, headshot"),
|
| 392 |
+
("ครึ่งตัวบน / upper body", "upper body shot, bust"),
|
| 393 |
+
("ครึ่งตัว เอวขึ้นไป / half body", "half body shot, waist up"),
|
| 394 |
+
("เต็มตัว หัวถึงเท้า / full body", "full body shot, head to toe, full body visible"),
|
| 395 |
+
("ระยะไกล เห็นรอบตัว / wide shot", "wide shot, full body, distant, environmental"),
|
| 396 |
+
("มุมเงย / low angle", "from below, low angle shot"),
|
| 397 |
+
("มุมก้ม / high angle", "from above, high angle shot"),
|
| 398 |
+
("มุมข้าง โปรไฟล์ / side", "from side, profile view"),
|
| 399 |
+
("มุมหลัง / from behind", "from behind, back view"),
|
| 400 |
+
("เซลฟี่ / selfie", "selfie, pov, arm extended"),
|
| 401 |
+
],
|
| 402 |
+
value=None, multiselect=True, allow_custom_value=True)
|
| 403 |
+
with gr.Row():
|
| 404 |
+
b_pose_img = gr.Image(
|
| 405 |
+
label="📷 รูปอ้างอิงท่าโพส (ใส่รูป → ล็อกท่าตามรูปด้วย ControlNet OpenPose · "
|
| 406 |
+
"หน้า/ชุด/ฉาก ยังคุมด้วยช่องด้านบน · เว้นว่าง = ใช้ข้อความท่าโพส)",
|
| 407 |
+
type="pil", sources=["upload"], height=240)
|
| 408 |
+
build_btn = gr.Button("✨ สร้าง prompt → ใส่ในช่องด้านบน", variant="secondary")
|
| 409 |
+
|
| 410 |
+
# State: ordered list of model ids (matches gallery order) + current selection.
|
| 411 |
+
ids_state = gr.State(model_ids(MODELS))
|
| 412 |
+
selected_id = gr.State(MODELS[0]["id"] if MODELS else None)
|
| 413 |
+
|
| 414 |
+
with gr.Row(equal_height=False):
|
| 415 |
+
# ---- left: model picker ----
|
| 416 |
+
with gr.Column(scale=1):
|
| 417 |
+
with gr.Group():
|
| 418 |
+
gr.Markdown("### 🧩 เลือกโมเดล / Models")
|
| 419 |
+
model_gallery = gr.Gallery(
|
| 420 |
+
value=gallery_items(MODELS),
|
| 421 |
+
label=None, show_label=False, columns=2, height="auto",
|
| 422 |
+
object_fit="cover", allow_preview=False, container=False,
|
| 423 |
+
elem_classes="card",
|
| 424 |
+
)
|
| 425 |
+
selected_md = gr.Markdown(
|
| 426 |
+
f"**เลือก / Selected:** {MODELS[0]['label']}" if MODELS else ""
|
| 427 |
+
)
|
| 428 |
+
reload_btn = gr.Button("🔄 Reload models", size="sm")
|
| 429 |
+
reload_status = gr.Markdown("")
|
| 430 |
+
|
| 431 |
+
mode_radio = gr.Radio(
|
| 432 |
+
choices=modes_for(MODELS, MODELS[0]["id"]) if MODELS else [],
|
| 433 |
+
value="txt2img",
|
| 434 |
+
label="โหมดรูปต้นแบบ / Input mode",
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
translator = gr.Radio(
|
| 438 |
+
choices=[("ปิด / Off", "off"),
|
| 439 |
+
("NLLB-200 (เร็ว)", "nllb"),
|
| 440 |
+
("Typhoon 2 (ไทยแน่น)", "typhoon")],
|
| 441 |
+
value="typhoon",
|
| 442 |
+
label="แปลไทย→อังกฤษ / Auto-translate (พิมพ์ไทยได้เลย)",
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
# ---- right: output ----
|
| 446 |
+
with gr.Column(scale=1):
|
| 447 |
+
output = gr.Image(label="Generated Image", height=560, elem_classes="card")
|
| 448 |
+
status = gr.Markdown("")
|
| 449 |
+
|
| 450 |
+
# ---- advanced ----
|
| 451 |
+
with gr.Accordion("Advanced Settings", open=False):
|
| 452 |
+
with gr.Row():
|
| 453 |
+
with gr.Column():
|
| 454 |
+
ref_image = gr.Image(label="Input image (รูปต้นแบบ)", type="pil", height=240)
|
| 455 |
+
ip_scale = gr.Slider(0.0, 1.5, value=0.7, step=0.05,
|
| 456 |
+
label="Reference strength (IP-Adapter / FaceID)")
|
| 457 |
+
denoise = gr.Slider(0.1, 1.0, value=0.65, step=0.01,
|
| 458 |
+
label="Denoise strength (img2img · ต่ำ = อิงรูปมาก)")
|
| 459 |
+
with gr.Column():
|
| 460 |
+
negative_prompt = gr.Textbox(label="Negative prompt", lines=2)
|
| 461 |
+
with gr.Row():
|
| 462 |
+
steps = gr.Slider(1, 50, value=28, step=1, label="Steps")
|
| 463 |
+
guidance = gr.Slider(0.0, 15.0, value=6.5, step=0.1, label="Guidance (CFG)")
|
| 464 |
+
with gr.Row():
|
| 465 |
+
width = gr.Slider(384, 1280, value=512, step=64, label="Width")
|
| 466 |
+
height = gr.Slider(384, 1280, value=768, step=64, label="Height")
|
| 467 |
+
with gr.Row():
|
| 468 |
+
seed = gr.Number(value=-1, label="Seed (-1 = random)", precision=0)
|
| 469 |
+
randomize = gr.Checkbox(value=True, label="Randomize seed")
|
| 470 |
|
| 471 |
# ---- wiring ----
|
| 472 |
model_gallery.select(
|
|
|
|
| 492 |
prompt.submit(generate, inputs=gen_inputs, outputs=[output, seed, status])
|
| 493 |
|
| 494 |
|
| 495 |
+
|
| 496 |
+
# ---- Tab 1 (identity) wiring ----
|
| 497 |
+
face_build_btn.click(
|
| 498 |
+
build_face_prompt,
|
| 499 |
+
inputs=[f_gender, f_age, f_ethnicity, f_faceshape, f_skin, f_eyecolor,
|
| 500 |
+
f_eyes, f_brow, f_nose, f_mouth, f_hair, f_expr],
|
| 501 |
+
outputs=face_prompt,
|
| 502 |
+
)
|
| 503 |
+
face_gen_btn.click(
|
| 504 |
+
generate_face,
|
| 505 |
+
inputs=[selected_id, face_prompt, translator],
|
| 506 |
+
outputs=[face_output, face_status],
|
| 507 |
+
)
|
| 508 |
+
send_face_btn.click(
|
| 509 |
+
send_face_to_scene,
|
| 510 |
+
inputs=[face_output],
|
| 511 |
+
outputs=[ref_image, mode_radio, send_note],
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
|
| 515 |
if __name__ == "__main__":
|
| 516 |
# allowed_paths lets Gradio serve the local model preview thumbnails.
|
| 517 |
demo.queue(max_size=12).launch(allowed_paths=["previews"])
|