renderfy commited on
Commit
4fbdf25
·
verified ·
1 Parent(s): b295003

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +200 -173
streamlit_app.py CHANGED
@@ -1,5 +1,5 @@
1
- # streamlit_app.py — AI LightBox · Jewelry (Frontend v0.6.0, Jewelry-tuned UI)
2
- # Not: "Previews" kartlarının DOM/yapısı korunmuştur. Diğer tüm UI yeniden tasarlandı (mücevher odaklı).
3
  import os, io, base64, zipfile, requests, streamlit as st
4
  from PIL import Image
5
 
@@ -7,14 +7,14 @@ from PIL import Image
7
  def apply_theme():
8
  THEME = {
9
  "base": "dark",
10
- "primaryColor": "#ff7a1a", # jewelry amber
11
- "backgroundColor": "#0e0f12", # obsidian
12
- "secondaryBackgroundColor": "#15171c",
13
- "panelBackgroundColor": "#1a1d23", # velvet panel
14
  "textColor": "#e7eaee",
15
- "mutedColor": "#98a2b3",
16
- "outline": "rgba(255,255,255,0.06)",
17
- "glow": "0 0 0 1px rgba(255,255,255,0.06) inset, 0 8px 24px rgba(255,122,26,0.10)",
 
18
  }
19
  try:
20
  st._config.set_option("theme.base", THEME["base"])
@@ -30,58 +30,102 @@ def apply_theme():
30
  :root {{
31
  --bg: {THEME["backgroundColor"]};
32
  --sb: {THEME["secondaryBackgroundColor"]};
33
- --panel: {THEME["panelBackgroundColor"]};
34
  --text: {THEME["textColor"]};
35
  --muted: {THEME["mutedColor"]};
36
  --primary: {THEME["primaryColor"]};
37
- --outline: {THEME["outline"]};
38
- --glow: {THEME["glow"]};
 
39
  }}
40
- html, body, .block-container {{ background: var(--bg) !important; color: var(--text) !important; }}
41
- .stApp header {{ background: var(--bg) !important; }}
42
-
43
- /* --- Jewelry Hero --- */
44
- .j-hero {{
45
- background: radial-gradient(1200px 400px at 10% -10%, rgba(255,122,26,0.10), transparent 60%),
46
- radial-gradient(800px 300px at 100% 0%, rgba(255,122,26,0.10), transparent 65%),
47
- linear-gradient(180deg, #111319, #0e0f12 40%);
48
- border-radius: 16px; padding: 22px 20px; margin-bottom: 16px; box-shadow: var(--glow);
49
- border: 1px solid rgba(255,255,255,0.04);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }}
51
- .j-title {{ font-weight:800; font-size: 1.35rem; color: var(--primary); letter-spacing: .3px; }}
52
- .j-sub {{ color: var(--muted); margin-top: 4px; }}
53
 
54
- /* --- Section Card (all non-Preview panels) --- */
55
- .j-card {{
56
- background: var(--panel); border-radius: 14px; padding: 14px 14px 12px 14px;
57
- border: 1px solid rgba(255,255,255,0.05); box-shadow: var(--glow); margin-bottom: 10px;
58
  }}
59
- .j-head {{
60
- display:flex; align-items:center; gap:10px; margin-bottom: 10px;
61
- color: var(--primary); font-weight: 700; font-size: 1.0rem;
62
  }}
63
- .j-chip {{
64
- display:inline-flex; align-items:center; gap:8px; padding:6px 10px; border-radius: 999px;
65
- background: rgba(255,122,26,0.10); color: var(--primary);
66
- border: 1px solid rgba(255,122,26,0.35); font-weight: 600; font-size: .85rem;
 
67
  }}
68
 
69
- /* --- Form cosmetics --- */
70
- label, [data-testid="stWidgetLabel"] p {{ color: var(--primary) !important; font-weight: 600; }}
71
- .stAlert, .stAlert p {{ color: var(--primary) !important; background: var(--panel) !important; border-radius: 12px; }}
72
- .stButton > button {{ background: var(--primary) !important; color: #0b0c10 !important; font-weight: 800;
73
- border: 0; border-radius: 12px; padding: 10px 14px; box-shadow: 0 6px 20px rgba(255,122,26,0.18); }}
74
- .stButton > button:hover {{ filter: brightness(1.07); }}
75
- .stDownloadButton > button {{ background: transparent !important; color: var(--primary) !important;
76
- border: 1px solid var(--primary); border-radius: 12px; }}
77
- .stCaption, .stMarkdown p small {{ color: var(--primary) !important; }}
78
-
79
- /* --- Divider --- */
80
- .j-divider {{ height: 1px; background: linear-gradient(90deg, transparent, rgba(255,122,26,0.7), transparent);
81
- margin: 12px 0 2px 0; }}
82
-
83
- /* Keep PREVIEW cards intact; only adjust titles color via class used below */
84
- .lb-card-title {{ color: var(--primary); font-weight: 800; }}
85
  </style>
86
  """, unsafe_allow_html=True)
87
 
@@ -215,16 +259,19 @@ def inject_preview_css(aspect: str, fit_mode="contain"):
215
  st.markdown(f"""
216
  <style>
217
  .lb-card-title {{
218
- font-weight:800; font-size:1.05rem; margin:0 0 8px 0; color: var(--primary);
219
  }}
220
  .lb-frame {{
221
  position: relative; width: 100%;
222
  aspect-ratio: {aw} / {ah};
223
- border-radius: 12px; overflow: hidden; background: #fff; /* Previews kartlarını aynen bırak */
224
  display:flex; align-items:center; justify-content:center;
225
- box-shadow: 0 0 0 1px rgba(255,255,255,0.06) inset;
 
 
 
 
226
  }}
227
- .lb-frame img {{ width:100%; height:100%; object-fit:{fit_mode}; display:block; }}
228
  .lb-video {{ display:flex; justify-content:center; }}
229
  .lb-video video {{ border-radius:8px; max-width: 540px; width:100%; height:auto; }}
230
  </style>
@@ -268,122 +315,108 @@ def show_tile_card_display_download(
268
 
269
  return display_url or "data", native
270
 
271
- # ======================= Local demo =======================
272
- def load_local_demo():
273
- demo = {}
274
- for fname in ["input.jpg", "packshot.jpg", "model.jpg", "tryon.mp4"]:
275
- if os.path.exists(fname):
276
- with open(fname, "rb") as f: demo[fname] = f.read()
277
- return demo
278
-
279
- demo_files = load_local_demo()
280
-
281
- # ======================= PAGE =======================
282
- st.set_page_config(page_title="AI LightBox · Jewelry", layout="wide", page_icon="💎")
283
-
284
- # --- HERO ---
285
  st.markdown("""
286
- <div class="j-hero">
287
- <div class="j-title">💎 AI LightBox · Jewelry</div>
288
- <div class="j-sub">Upload. Try-on. Download. Packshot & Model compositing optimized for jewelry.</div>
289
- <div class="j-divider"></div>
290
  </div>
291
  """, unsafe_allow_html=True)
292
 
 
 
293
  ok = backend_ok()
294
- use_rmbg = (st.session_state["health"].get("use_rmbg") if isinstance(st.session_state["health"], dict) else None) or "unknown"
295
  version = st.session_state["health"].get("version", "—")
296
 
297
- # --- Top Status Chips ---
298
- c0, c1, c2, c3 = st.columns([1.1, 1.1, 1.1, 3.7])
299
- with c0: st.markdown(f'<div class="j-chip">🔌 Connection: {"Online" if ok else "Offline"}</div>', unsafe_allow_html=True)
300
- with c1: st.markdown(f'<div class="j-chip">🧠 Backend v{version}</div>', unsafe_allow_html=True)
301
- with c2: st.markdown(f'<div class="j-chip">🪄 RMBG: {use_rmbg}</div>', unsafe_allow_html=True)
302
-
303
- # --- Panels (do NOT touch Previews section later) ---
304
- left, right = st.columns([1.15, 1.0])
305
-
306
- with left:
307
- # INPUT PANEL
308
- st.markdown('<div class="j-card"><div class="j-head">📥 Product Input</div>', unsafe_allow_html=True)
309
- col1, col2 = st.columns([1.4, 1.1])
310
- with col1:
311
- file_list = st.file_uploader("Product Image (Upload)", type=["jpg","jpeg","png","webp"], accept_multiple_files=True, key="file_upl")
312
- with col2:
313
- image_url = st.text_input("Or URL", key="image_url")
314
- st.markdown('</div>', unsafe_allow_html=True)
315
-
316
- # CANVAS PANEL
317
- st.markdown('<div class="j-card"><div class="j-head">🖼️ Canvas / Preview</div>', unsafe_allow_html=True)
318
- colT1, colT2, colT3 = st.columns([1.0, 1.0, 1.0])
319
- with colT1:
320
- preview_aspect = st.selectbox("Preview Frame Ratio", ASPECT_OPTIONS, index=0, key="preview_aspect")
321
- with colT2:
322
- preview_long_edge = st.number_input("Preview Long Edge (px)", min_value=256, max_value=8192, value=1920, step=64, key="preview_long_edge")
323
- with colT3:
324
- fit_mode = st.selectbox("Preview Layout", ["fit","fill"], index=0, help="Sadece önizlemeyi etkiler.", key="fit_mode")
325
- st.markdown('</div>', unsafe_allow_html=True)
326
-
327
- # ACTION PANEL
328
- st.markdown('<div class="j-card"><div class="j-head">🚀 Generate</div>', unsafe_allow_html=True)
329
- colA, colB, colC = st.columns([1.2, 1.2, 1.0])
330
- with colA:
331
- run = st.button("Run 💎", type="primary", use_container_width=True, key="run_btn")
332
- with colB:
333
- st.caption("Tip: First run will warm up models.")
334
- with colC:
335
- st.caption("All outputs served from /outputs/*")
336
- st.markdown('</div>', unsafe_allow_html=True)
337
-
338
- with right:
339
- # PRODUCT/GENERATION PANEL
340
- st.markdown('<div class="j-card"><div class="j-head">⚙️ Product · Generation</div>', unsafe_allow_html=True)
341
- col3, col4 = st.columns([1.0, 1.0])
342
- with col3:
343
- jewel_type = st.selectbox("Type", ["auto","earring","ring","necklace","bracelet","pendant","brooch","set","watch"], index=0, key="jewel_type")
344
- preview_ratio = st.number_input("Preview Scale (0.10–0.99)", min_value=0.10, max_value=0.99, value=0.50, step=0.01,
345
- help="Sadece önizleme kadrajında büyüklüğü etkiler.", key="preview_ratio")
346
- identity_lock = st.checkbox("Identity lock", True, key="identity_lock")
347
- with col4:
348
- upscale = st.checkbox("Super Res", False, key="upscale")
349
- upscale_stage = st.selectbox("SR Step", ["both","final","packshot"], index=0, help="Varsayılan: both", key="upscale_stage")
350
- upscale_factor = st.selectbox("SR Factor", ["2","4"], index=0, key="upscale_factor")
351
- st.markdown('<div class="j-divider"></div>', unsafe_allow_html=True)
352
- col5, col6, col7 = st.columns([1.0, 1.0, 1.0])
353
- with col5:
354
- to_video = st.checkbox("One Click Generate Video", False, key="to_video")
355
- with col6:
356
- duration = st.selectbox("Duration ", VIDEO_DUR_OPTIONS, index=0, key="duration_sel")
357
- with col7:
358
- resolution = st.selectbox("Video Res", VIDEO_RES_OPTIONS, index=1, key="resolution_sel")
359
- st.markdown('</div>', unsafe_allow_html=True)
360
-
361
- # Inject CSS for PREVIEW CARDS ONLY (structure intact)
362
- inject_preview_css(preview_aspect, fit_mode="contain" if fit_mode=="fit" else "cover")
363
 
364
- # ======================= PRE-RUN PREVIEWS (UNCHANGED LAYOUT) =======================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  st.subheader("Previews")
366
  col_in, col_pack, col_pad, col_vid = st.columns(4)
367
 
368
- # Input bytes
369
  input_bytes = None
370
  if file_list:
371
  try: input_bytes = file_list[0].getvalue()
372
  except Exception: input_bytes = None
373
  elif image_url:
374
  input_bytes = fetch_bytes(image_url)
375
- elif demo_files.get("input.jpg"):
376
- input_bytes = demo_files["input.jpg"]
 
 
377
 
378
  show_tile_card_display_download(
379
  col_in, "Sample Input preview",
380
- display_bytes=input_bytes if input_bytes else demo_files.get("input.jpg"),
381
- download_bytes=input_bytes if input_bytes else demo_files.get("input.jpg"),
382
  filename_stub="input", key_prefix="pre"
383
  )
384
 
385
- # Packshot preview (demo dosyası varsa; yoksa input)
386
- pack_bytes_demo = demo_files.get("packshot.jpg") or input_bytes
 
 
 
 
 
387
  show_tile_card_display_download(
388
  col_pack, "Sample Packshot (BG Removed)",
389
  display_bytes=pack_bytes_demo,
@@ -391,19 +424,18 @@ show_tile_card_display_download(
391
  filename_stub="packshot", key_prefix="pre"
392
  )
393
 
394
- # Padded preview (UI-only)
395
  padded_bytes = None
396
  if pack_bytes_demo:
397
  try:
398
  im = Image.open(io.BytesIO(pack_bytes_demo)).convert("RGBA")
399
- aw, ah = (lambda s: (int(s.split(":")[0]), int(s.split(":")[1])))(preview_aspect)
400
  if aw >= ah:
401
- W = int(preview_long_edge); H = max(1, int(W * ah / aw))
402
  else:
403
- H = int(preview_long_edge); W = max(1, int(H * aw / ah))
404
- ratio = max(0.10, min(0.99, float(preview_ratio)))
405
  w, h = im.size
406
- target = int(min(W, H) * ratio if aw == ah else (H * ratio if aw < ah else W * ratio))
407
  if w >= h: new_w, new_h = target, max(1, int(h * (target / max(1, w))))
408
  else: new_h, new_w = target, max(1, int(w * (target / max(1, h))))
409
  im_resized = im.resize((new_w, new_h), Image.LANCZOS)
@@ -421,8 +453,10 @@ show_tile_card_display_download(
421
  filename_stub="padded_ui", key_prefix="pre"
422
  )
423
 
424
- # Video preview (loop; yerel tryon.mp4 varsa)
425
- vid_demo = demo_files.get("tryon.mp4")
 
 
426
  col_vid.markdown('<div class="lb-card-title">AI Generated Video Preview</div>', unsafe_allow_html=True)
427
  if vid_demo:
428
  data_url = "data:video/mp4;base64," + base64.b64encode(vid_demo).decode("ascii")
@@ -443,8 +477,8 @@ else:
443
  # ======================= RUN =======================
444
  if run:
445
  if not ok:
446
- st.error("Backend erişilemiyor. API_BASE / TOKEN kontrol edin.")
447
- elif not (file_list or image_url or demo_files.get("input.jpg")):
448
  st.error("En az bir görsel girişi yapın (dosya yükle veya URL).")
449
  else:
450
  try:
@@ -456,21 +490,18 @@ if run:
456
 
457
  data = {
458
  "category": st.session_state.get("jewel_type","auto"),
459
- "edit_prompt": "", # backend normalize ediyor
460
  "num_images": "1",
461
  "image_urls": img_urls,
462
  "mannequin_image_url": "",
463
  "identity_lock": "true" if st.session_state.get("identity_lock", True) else "false",
464
- # Preview/Pad controls
465
  "preview_aspect": st.session_state.get("preview_aspect","9:16"),
466
  "preview_long_edge": str(int(st.session_state.get("preview_long_edge", 1920))),
467
  "preview_ratio": str(float(st.session_state.get("preview_ratio", 0.5))),
468
- "padding_ratio": str(float(st.session_state.get("preview_ratio", 0.5))), # backward-compat
469
- # SR
470
  "upscale": "true" if st.session_state.get("upscale", False) else "false",
471
  "upscale_factor": st.session_state.get("upscale_factor","2"),
472
  "upscale_stage": st.session_state.get("upscale_stage","both"),
473
- # Video (optional inside chain)
474
  "to_video": "true" if st.session_state.get("to_video", False) else "false",
475
  "video_prompt": "",
476
  "duration": st.session_state.get("duration_sel", VIDEO_DUR_OPTIONS[0]),
@@ -478,18 +509,16 @@ if run:
478
  "prompt_optimizer": "false",
479
  }
480
 
481
- with st.spinner("AI Chain Running…"):
482
  out = post_chain(data, files_payload if files_payload else None)
483
 
484
  st.session_state["job_counter"] += 1
485
 
486
- # Parse outputs (v0.6.0)
487
  pack_native_url = (out.get("packshot_step", {}).get("result", {}).get("images_native") or [{}])[0].get("url")
488
  padded_native_url = (out.get("packshot_step", {}).get("result", {}).get("padded_native") or [{}])[0].get("url")
489
  placed_native_url = (out.get("image_step", {}).get("result", {}).get("images_native") or [{}])[0].get("url")
490
  vurl = (out.get("video_step") or {}).get("result", {}).get("video", {}).get("url")
491
 
492
- # DISPLAY small JPGs (dark-friendly)
493
  pack_display_jpeg = None
494
  if pack_native_url:
495
  pn = fetch_bytes(pack_native_url)
@@ -527,7 +556,7 @@ if run:
527
  res = st.session_state.get("results")
528
  vbytes = None
529
  if res:
530
- st.markdown('<div class="j-card"><div class="j-head">📦 Product Results</div>', unsafe_allow_html=True)
531
  c_pack, c_pad, c_model, c_vid = st.columns(4)
532
 
533
  show_tile_card_display_download(
@@ -588,13 +617,12 @@ if res:
588
  vurl2 = (v.get("result") or {}).get("video", {}).get("url")
589
  if vurl2:
590
  st.session_state["results"]["video_url"] = vurl2
591
- st.success("Video is Ready!.")
592
  else:
593
  st.info("Video URL gelmedi.")
594
  st.markdown('</div>', unsafe_allow_html=True)
595
 
596
- # ZIP bundle
597
- st.markdown('<div class="j-card"><div class="j-head">📦 Export</div>', unsafe_allow_html=True)
598
  named = []
599
  pn = fetch_bytes(res.get("packshot_native_url")) if res.get("packshot_native_url") else None
600
  mn = fetch_bytes(res.get("placed_native_url")) if res.get("placed_native_url") else None
@@ -610,13 +638,12 @@ if res:
610
  st.download_button("Download All (ZIP)", data=buf.read(),
611
  file_name="ai_lightbox_outputs.zip", mime="application/zip",
612
  key=f"job{st.session_state['job_counter']}_zip_dl")
613
- st.markdown('</div>', unsafe_allow_html=True)
614
 
615
- # ======================= Sidebar =======================
616
  with st.sidebar:
 
617
  st.caption(f"API_BASE: {API_BASE}")
618
  if HF_TOKEN: st.caption("Auth: Bearer (aktif)")
619
- st.caption(f"Backend RMBG policy: {use_rmbg}")
620
  st.slider("Video önizleme genişliği (px)", 360, 1080, st.session_state["video_ui_max_px"],
621
  step=10, key="video_ui_max_px")
622
  if st.button("Önbelleği temizle"): st.cache_data.clear(); st.success("Önbellek temizlendi.")
 
1
+ # streamlit_app.py — AI LightBox · Jewelry (Frontend · v0.6.0)
2
+ # Not: "Previews" kartlarının yapısı korunmuştur. Diğer UI bölümleri jewelry uyumlu modern tasarımla güncellendi.
3
  import os, io, base64, zipfile, requests, streamlit as st
4
  from PIL import Image
5
 
 
7
  def apply_theme():
8
  THEME = {
9
  "base": "dark",
10
+ "primaryColor": "#ff7a1a", # jewelry orange
11
+ "backgroundColor": "#0e0f12", # deep onyx
12
+ "secondaryBackgroundColor": "#14161b",# smoky quartz
 
13
  "textColor": "#e7eaee",
14
+ "mutedColor": "#9aa1ab",
15
+ "cardOutline": "rgba(255,255,255,0.06)",
16
+ "accent2": "#ffd6b0", # soft amber
17
+ "accent3": "#2a2f3a", # hematite
18
  }
19
  try:
20
  st._config.set_option("theme.base", THEME["base"])
 
30
  :root {{
31
  --bg: {THEME["backgroundColor"]};
32
  --sb: {THEME["secondaryBackgroundColor"]};
 
33
  --text: {THEME["textColor"]};
34
  --muted: {THEME["mutedColor"]};
35
  --primary: {THEME["primaryColor"]};
36
+ --outline: {THEME["cardOutline"]};
37
+ --accent2: {THEME["accent2"]};
38
+ --accent3: {THEME["accent3"]};
39
  }}
40
+
41
+ /* Background with subtle vignette */
42
+ .stApp {{
43
+ background:
44
+ radial-gradient(1200px 800px at 0% 0%, rgba(255,122,26,0.06), transparent 50%),
45
+ radial-gradient(1000px 700px at 100% 100%, rgba(255,122,26,0.04), transparent 50%),
46
+ var(--bg) !important;
47
+ }}
48
+ .block-container {{ padding-top: 1.25rem !important; }}
49
+
50
+ /* Headline hero */
51
+ .hero {{
52
+ padding: 16px 18px;
53
+ border-radius: 16px;
54
+ background: linear-gradient(180deg, rgba(255,122,26,0.12) 0%, rgba(255,122,26,0.06) 100%);
55
+ box-shadow: 0 0 0 1px rgba(255,122,26,0.18) inset, 0 6px 30px rgba(0,0,0,0.35);
56
+ }}
57
+ .hero h1 {{
58
+ margin: 0; padding: 0;
59
+ font-size: 1.4rem; line-height: 1.3; letter-spacing: .2px;
60
+ color: var(--accent2);
61
+ }}
62
+ .hero p {{ margin: 4px 0 0 0; color: var(--text); opacity:.9; }}
63
+
64
+ /* Panels (controls) */
65
+ .panel {{
66
+ background: linear-gradient(180deg, rgba(20,22,27,0.9) 0%, rgba(20,22,27,0.75) 100%);
67
+ border-radius: 16px;
68
+ padding: 14px 14px 8px 14px;
69
+ margin-top: 12px;
70
+ box-shadow: 0 0 0 1px var(--outline) inset, 0 10px 30px rgba(0,0,0,0.35);
71
+ }}
72
+ .panel h3 {{
73
+ margin: 0 0 10px 0; font-size: 1.05rem; font-weight: 700; color: var(--primary);
74
+ display: inline-flex; align-items: center; gap: 8px;
75
+ }}
76
+ .chipbar {{ display:flex; flex-wrap: wrap; gap:8px; margin:6px 0 2px 0; }}
77
+ .chip {{
78
+ display:inline-flex; align-items:center; gap:8px; padding:6px 10px; border-radius:999px;
79
+ background: rgba(255,122,26,0.10); color: var(--accent2); border: 1px solid rgba(255,122,26,0.25);
80
+ font-size:.85rem; font-weight:600; letter-spacing:.2px;
81
+ }}
82
+ .chip .dot {{ width:8px; height:8px; border-radius:50%; background:#2ee06b; box-shadow:0 0 0 2px rgba(46,224,107,0.15); }}
83
+ .chip.muted {{ background: rgba(154,161,171,0.10); color: var(--text); border-color: rgba(154,161,171,0.25); }}
84
+ .chip.badge {{ background: rgba(42,47,58,0.9); color: var(--text); border-color: rgba(255,255,255,0.08); }}
85
+
86
+ /* Make labels and captions jewelry-orange */
87
+ label, [data-testid="stWidgetLabel"] p, .lb-card-title, h2, h3, .stCaption, .stMarkdown > p {{
88
+ color: var(--primary) !important;
89
+ }}
90
+
91
+ /* Buttons */
92
+ .stButton > button {{
93
+ background: var(--primary) !important; color: #101215 !important; font-weight: 800;
94
+ border: none; border-radius: 12px; padding: .55rem .9rem;
95
+ box-shadow: 0 0 0 1px #00000033 inset, 0 8px 18px rgba(255,122,26,0.25);
96
+ transition: transform .08s ease;
97
+ }}
98
+ .stButton > button:hover {{ filter: brightness(1.06); transform: translateY(-1px); }}
99
+ .stDownloadButton > button {{
100
+ background: transparent !important; color: var(--primary) !important;
101
+ border: 1px solid var(--primary); border-radius: 12px; font-weight:700;
102
  }}
103
+ .stDownloadButton > button:hover {{ filter: brightness(1.1); }}
 
104
 
105
+ /* Inputs look crisper on dark */
106
+ .stTextInput, .stNumberInput, .stSelectbox, .stFileUploader, .stCheckbox, .stSlider, .stRadio {{
107
+ background: var(--accent3) !important; border-radius: 12px !important;
108
+ box-shadow: 0 0 0 1px var(--outline) inset !important; padding: 6px 8px !important;
109
  }}
110
+ [data-testid="stFileUploaderDropzone"] {{
111
+ background: rgba(42,47,58,0.6); border: 1px dashed rgba(255,255,255,0.15); border-radius: 12px;
 
112
  }}
113
+
114
+ /* Alerts -> on-brand */
115
+ .stAlert, .stAlert p, .stAlert span {{
116
+ color: var(--accent2) !important; background: rgba(255,122,26,0.08) !important;
117
+ border-radius: 12px; box-shadow: 0 0 0 1px rgba(255,122,26,0.25) inset;
118
  }}
119
 
120
+ /* ---- Previews cards (UNCHANGED structure) ---- */
121
+ .lb-card-title {{
122
+ font-weight:700; font-size:1.15rem; margin:0 0 8px 0; color: var(--primary);
123
+ }}
124
+ .lb-frame {{
125
+ border-radius: 12px; overflow: hidden; background: var(--sb);
126
+ display:flex; align-items:center; justify-content:center;
127
+ box-shadow: 0 0 0 1px var(--outline) inset;
128
+ }}
 
 
 
 
 
 
 
129
  </style>
130
  """, unsafe_allow_html=True)
131
 
 
259
  st.markdown(f"""
260
  <style>
261
  .lb-card-title {{
262
+ font-weight:700; font-size:1.15rem; margin:0 0 8px 0; color: var(--primary);
263
  }}
264
  .lb-frame {{
265
  position: relative; width: 100%;
266
  aspect-ratio: {aw} / {ah};
267
+ border-radius: 12px; overflow: hidden; background: var(--sb);
268
  display:flex; align-items:center; justify-content:center;
269
+ box-shadow: 0 0 0 1px var(--outline) inset;
270
+ }}
271
+ .lb-frame img {{
272
+ width:100%; height:100%; object-fit:{fit_mode};
273
+ display:block;
274
  }}
 
275
  .lb-video {{ display:flex; justify-content:center; }}
276
  .lb-video video {{ border-radius:8px; max-width: 540px; width:100%; height:auto; }}
277
  </style>
 
315
 
316
  return display_url or "data", native
317
 
318
+ # ======================= Hero =======================
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  st.markdown("""
320
+ <div class="hero">
321
+ <h1>💎 AI LightBox · Jewelry Studio</h1>
322
+ <p>Product packshot, mannequin placement ve kısa video tek akışta.</p>
 
323
  </div>
324
  """, unsafe_allow_html=True)
325
 
326
+ # ======================= Page =======================
327
+ st.set_page_config(page_title="AI LightBox · Jewelry", layout="wide", page_icon="💎")
328
  ok = backend_ok()
329
+ use_rmbg = (st.session_state["health"].get("use_rmbg") if isinstance(st.session_state["health"], dict) else None) or ""
330
  version = st.session_state["health"].get("version", "—")
331
 
332
+ # -------- Status chips (compact) --------
333
+ st.markdown(f"""
334
+ <div class="chipbar">
335
+ <div class="chip"><span class="dot"></span>{'Online' if ok else 'Offline'}</div>
336
+ <div class="chip badge">Backend v{version}</div>
337
+ <div class="chip muted">RMBG: {use_rmbg}</div>
338
+ </div>
339
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
 
341
+ # -------- Panels: Inputs · Studio Controls · Video --------
342
+ st.markdown('<div class="panel"> <h3>🧰 Inputs</h3>', unsafe_allow_html=True)
343
+ col1, col2, col3 = st.columns([1.6, 1.6, 1.0])
344
+ with col1:
345
+ file_list = st.file_uploader("Product Image (Upload)", type=["jpg","jpeg","png","webp"], accept_multiple_files=True, key="file_upl")
346
+ with col2:
347
+ image_url = st.text_input("Or URL", key="image_url")
348
+ with col3:
349
+ jewel_type = st.selectbox("Type", ["auto","earring","ring","necklace","bracelet","pendant","brooch","set","watch"], index=0, key="jewel_type")
350
+ st.markdown('</div>', unsafe_allow_html=True)
351
+
352
+ st.markdown('<div class="panel"> <h3>🪄 Studio Controls</h3>', unsafe_allow_html=True)
353
+ colS1, colS2, colS3, colS4 = st.columns([1.1, 1.1, 1.1, 1.1])
354
+ with colS1:
355
+ preview_ratio = st.number_input("Preview Scale (0.10–0.99)", min_value=0.10, max_value=0.99, value=0.50, step=0.01,
356
+ help="Sadece önizleme kadrajında büyüklüğü etkiler.", key="preview_ratio")
357
+ with colS2:
358
+ preview_aspect = st.selectbox("Preview Frame Ratio", ASPECT_OPTIONS, index=0, key="preview_aspect")
359
+ with colS3:
360
+ preview_long_edge = st.number_input("Preview Long Edge (px)", min_value=256, max_value=8192, value=1920, step=64, key="preview_long_edge")
361
+ with colS4:
362
+ fit_mode = st.selectbox("Preview Layout", ["fit","fill"], index=0, help="Sadece önizlemeyi etkiler.", key="fit_mode")
363
+
364
+ colS5, colS6, colS7, colS8 = st.columns([1.0, 1.0, 1.0, 1.0])
365
+ with colS5:
366
+ upscale = st.checkbox("Super Res", False, key="upscale")
367
+ with colS6:
368
+ upscale_stage = st.selectbox("SR Step", ["both","final","packshot"], index=0, help="Varsayılan: both", key="upscale_stage")
369
+ with colS7:
370
+ upscale_factor = st.selectbox("SR Factor", ["2","4"], index=0, key="upscale_factor")
371
+ with colS8:
372
+ identity_lock = st.checkbox("Identity lock", True, key="identity_lock")
373
+ st.markdown('</div>', unsafe_allow_html=True)
374
+
375
+ st.markdown('<div class="panel"> <h3>🎬 Video</h3>', unsafe_allow_html=True)
376
+ colV1, colV2, colV3, colV4 = st.columns([1.0, 1.0, 1.0, 1.0])
377
+ with colV1:
378
+ to_video = st.checkbox("One Click Generate Video", False, key="to_video")
379
+ with colV2:
380
+ duration = st.selectbox("Duration ", VIDEO_DUR_OPTIONS, index=0, key="duration_sel")
381
+ with colV3:
382
+ resolution = st.selectbox("Video Res", VIDEO_RES_OPTIONS, index=1, key="resolution_sel")
383
+ with colV4:
384
+ run = st.button("Run 💎", type="primary", use_container_width=True, key="run_btn")
385
+ st.markdown('</div>', unsafe_allow_html=True)
386
+
387
+ # Inject CSS for preview frames (kept as is)
388
+ inject_preview_css(st.session_state.get("preview_aspect","9:16"),
389
+ fit_mode="contain" if st.session_state.get("fit_mode","fit")=="fit" else "cover")
390
+
391
+ # ======================= PREVIEWS (UNCHANGED STRUCTURE) =======================
392
  st.subheader("Previews")
393
  col_in, col_pack, col_pad, col_vid = st.columns(4)
394
 
 
395
  input_bytes = None
396
  if file_list:
397
  try: input_bytes = file_list[0].getvalue()
398
  except Exception: input_bytes = None
399
  elif image_url:
400
  input_bytes = fetch_bytes(image_url)
401
+ else:
402
+ demo = "input.jpg"
403
+ if os.path.exists(demo):
404
+ with open(demo, "rb") as f: input_bytes = f.read()
405
 
406
  show_tile_card_display_download(
407
  col_in, "Sample Input preview",
408
+ display_bytes=input_bytes,
409
+ download_bytes=input_bytes,
410
  filename_stub="input", key_prefix="pre"
411
  )
412
 
413
+ pack_bytes_demo = None
414
+ demo_pack = "packshot.jpg"
415
+ if os.path.exists(demo_pack):
416
+ with open(demo_pack, "rb") as f: pack_bytes_demo = f.read()
417
+ if not pack_bytes_demo:
418
+ pack_bytes_demo = input_bytes
419
+
420
  show_tile_card_display_download(
421
  col_pack, "Sample Packshot (BG Removed)",
422
  display_bytes=pack_bytes_demo,
 
424
  filename_stub="packshot", key_prefix="pre"
425
  )
426
 
 
427
  padded_bytes = None
428
  if pack_bytes_demo:
429
  try:
430
  im = Image.open(io.BytesIO(pack_bytes_demo)).convert("RGBA")
431
+ aw, ah = (lambda s: (int(s.split(":")[0]), int(s.split(":")[1])))(st.session_state.get("preview_aspect","9:16"))
432
  if aw >= ah:
433
+ W = int(st.session_state.get("preview_long_edge", 1920)); H = max(1, int(W * ah / aw))
434
  else:
435
+ H = int(st.session_state.get("preview_long_edge", 1920)); W = max(1, int(H * aw / ah))
436
+ ratio_val = max(0.10, min(0.99, float(st.session_state.get("preview_ratio", 0.5))))
437
  w, h = im.size
438
+ target = int(min(W, H) * ratio_val if aw == ah else (H * ratio_val if aw < ah else W * ratio_val))
439
  if w >= h: new_w, new_h = target, max(1, int(h * (target / max(1, w))))
440
  else: new_h, new_w = target, max(1, int(w * (target / max(1, h))))
441
  im_resized = im.resize((new_w, new_h), Image.LANCZOS)
 
453
  filename_stub="padded_ui", key_prefix="pre"
454
  )
455
 
456
+ vid_demo = None
457
+ demo_vid = "tryon.mp4"
458
+ if os.path.exists(demo_vid):
459
+ with open(demo_vid, "rb") as f: vid_demo = f.read()
460
  col_vid.markdown('<div class="lb-card-title">AI Generated Video Preview</div>', unsafe_allow_html=True)
461
  if vid_demo:
462
  data_url = "data:video/mp4;base64," + base64.b64encode(vid_demo).decode("ascii")
 
477
  # ======================= RUN =======================
478
  if run:
479
  if not ok:
480
+ st.error("Bağlantı sorunu. API_BASE / TOKEN kontrol edin.")
481
+ elif not (file_list or image_url or input_bytes):
482
  st.error("En az bir görsel girişi yapın (dosya yükle veya URL).")
483
  else:
484
  try:
 
490
 
491
  data = {
492
  "category": st.session_state.get("jewel_type","auto"),
493
+ "edit_prompt": "",
494
  "num_images": "1",
495
  "image_urls": img_urls,
496
  "mannequin_image_url": "",
497
  "identity_lock": "true" if st.session_state.get("identity_lock", True) else "false",
 
498
  "preview_aspect": st.session_state.get("preview_aspect","9:16"),
499
  "preview_long_edge": str(int(st.session_state.get("preview_long_edge", 1920))),
500
  "preview_ratio": str(float(st.session_state.get("preview_ratio", 0.5))),
501
+ "padding_ratio": str(float(st.session_state.get("preview_ratio", 0.5))),
 
502
  "upscale": "true" if st.session_state.get("upscale", False) else "false",
503
  "upscale_factor": st.session_state.get("upscale_factor","2"),
504
  "upscale_stage": st.session_state.get("upscale_stage","both"),
 
505
  "to_video": "true" if st.session_state.get("to_video", False) else "false",
506
  "video_prompt": "",
507
  "duration": st.session_state.get("duration_sel", VIDEO_DUR_OPTIONS[0]),
 
509
  "prompt_optimizer": "false",
510
  }
511
 
512
+ with st.spinner("AI chain çalışıyor…"):
513
  out = post_chain(data, files_payload if files_payload else None)
514
 
515
  st.session_state["job_counter"] += 1
516
 
 
517
  pack_native_url = (out.get("packshot_step", {}).get("result", {}).get("images_native") or [{}])[0].get("url")
518
  padded_native_url = (out.get("packshot_step", {}).get("result", {}).get("padded_native") or [{}])[0].get("url")
519
  placed_native_url = (out.get("image_step", {}).get("result", {}).get("images_native") or [{}])[0].get("url")
520
  vurl = (out.get("video_step") or {}).get("result", {}).get("video", {}).get("url")
521
 
 
522
  pack_display_jpeg = None
523
  if pack_native_url:
524
  pn = fetch_bytes(pack_native_url)
 
556
  res = st.session_state.get("results")
557
  vbytes = None
558
  if res:
559
+ st.markdown('<div class="panel"><h3>🧾 Product Results</h3>', unsafe_allow_html=True)
560
  c_pack, c_pad, c_model, c_vid = st.columns(4)
561
 
562
  show_tile_card_display_download(
 
617
  vurl2 = (v.get("result") or {}).get("video", {}).get("url")
618
  if vurl2:
619
  st.session_state["results"]["video_url"] = vurl2
620
+ st.success("Video hazır.")
621
  else:
622
  st.info("Video URL gelmedi.")
623
  st.markdown('</div>', unsafe_allow_html=True)
624
 
625
+ # Bundle ZIP
 
626
  named = []
627
  pn = fetch_bytes(res.get("packshot_native_url")) if res.get("packshot_native_url") else None
628
  mn = fetch_bytes(res.get("placed_native_url")) if res.get("placed_native_url") else None
 
638
  st.download_button("Download All (ZIP)", data=buf.read(),
639
  file_name="ai_lightbox_outputs.zip", mime="application/zip",
640
  key=f"job{st.session_state['job_counter']}_zip_dl")
 
641
 
642
+ # ======================= Sidebar (compact) =======================
643
  with st.sidebar:
644
+ st.markdown("### ⚙️ Studio Settings")
645
  st.caption(f"API_BASE: {API_BASE}")
646
  if HF_TOKEN: st.caption("Auth: Bearer (aktif)")
 
647
  st.slider("Video önizleme genişliği (px)", 360, 1080, st.session_state["video_ui_max_px"],
648
  step=10, key="video_ui_max_px")
649
  if st.button("Önbelleği temizle"): st.cache_data.clear(); st.success("Önbellek temizlendi.")