linoyts HF Staff commited on
Commit
a9187e0
Β·
verified Β·
1 Parent(s): 498a04b

Add 'Train with your agent' hand-off button

Browse files
Files changed (1) hide show
  1. app.py +148 -1
app.py CHANGED
@@ -100,6 +100,23 @@ button.primary {box-shadow:0 6px 22px rgba(255,140,50,0.28);}
100
  /* file drop zone: a single input-shade panel (kill the lighter inner button) + readable text */
101
  #ds-upload {background:rgba(255,255,255,0.04) !important;}
102
  #ds-upload * {background-color:transparent !important; color:#cfcabf !important;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  footer {visibility:hidden;}
104
  """
105
 
@@ -169,6 +186,111 @@ PROFILES = {
169
  }
170
 
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  def _apply_profile(profile: str, rank_dirty: bool, alpha_dirty: bool, flavor_dirty: bool):
173
  """Cascade a profile to (quantization, optimizer, te_8bit, rank, alpha, manual-group visibility,
174
  flavor). rank/alpha/flavor respect dirty flags (preserve manual edits); Custom reveals manual
@@ -269,10 +391,31 @@ with gr.Blocks(title="LTX-2.3 LoRA Trainer") as demo:
269
  "Train a LoRA / IC-LoRA on your own videos β€” runs on **HF Jobs**, pushed to your Hub.",
270
  elem_id="hero",
271
  )
272
- gr.LoginButton(scale=0, min_width=200)
 
 
 
273
 
274
  banner = gr.Markdown(elem_id="banner")
275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  with gr.Tabs():
277
  # ----------------------------------------------------------------- 1 Β· Dataset
278
  with gr.Tab("1 Β· Dataset"):
@@ -410,6 +553,10 @@ with gr.Blocks(title="LTX-2.3 LoRA Trainer") as demo:
410
  ).then(_extract_id, inputs=joblink, outputs=job_id)
411
  refresh_btn.click(refresh, inputs=[job_id], outputs=[mon_status, mon_logs])
412
 
 
 
 
 
413
 
414
  if __name__ == "__main__":
415
  demo.queue(default_concurrency_limit=2).launch(theme=THEME, css=CSS)
 
100
  /* file drop zone: a single input-shade panel (kill the lighter inner button) + readable text */
101
  #ds-upload {background:rgba(255,255,255,0.04) !important;}
102
  #ds-upload * {background-color:transparent !important; color:#cfcabf !important;}
103
+ /* agent hand-off modal β€” CSS overlay on a gr.Column, shown/hidden via its `visible` toggle.
104
+ `visible=False` sets display:none (overrides this id's display:flex), so it stays hidden. */
105
+ #agent-modal {position:fixed; inset:0; z-index:1000; display:flex; align-items:center; justify-content:center;
106
+ background:rgba(0,0,0,0.55) !important; backdrop-filter:blur(4px); border:none !important; padding:18px;}
107
+ /* our id selector (specificity 100) would otherwise beat gradio's `.hide` (10) and keep the
108
+ modal on screen after Close β€” this restores the hide when `visible=False` adds `.hide`. */
109
+ #agent-modal.hide {display:none !important;}
110
+ #agent-modal-card {max-width:940px; width:94%; max-height:88vh; overflow:auto;
111
+ background:#1e2024 !important; border:1px solid rgba(255,255,255,0.18) !important;
112
+ border-radius:16px !important; padding:22px 24px !important;
113
+ box-shadow:0 24px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.05);}
114
+ #agent-open-btn {white-space:nowrap;}
115
+ /* tighten the modal heading→text gap and make inline `code` readable on the dark card */
116
+ #agent-modal-card h1, #agent-modal-card h2, #agent-modal-card h3 {margin:0 0 4px !important;}
117
+ #agent-modal-card p {margin:.15rem 0 !important;}
118
+ #agent-modal-card code {background:rgba(255,255,255,0.12) !important; color:#f2efe9 !important;
119
+ padding:1px 6px; border-radius:5px;}
120
  footer {visibility:hidden;}
121
  """
122
 
 
186
  }
187
 
188
 
189
+ def _fmt_lr(v: float) -> str:
190
+ """2e-4 / 1e-4 style (matches the LR field's helper text)."""
191
+ return f"{v:.0e}".replace("e-0", "e-").replace("e+0", "e+")
192
+
193
+
194
+ def _build_agent_playbook() -> str:
195
+ """Assemble the copy-paste agent playbook from the SAME constants the UI uses
196
+ (jobs.MODES, PROFILES, MODE_HELP, FLAVOR_GUIDE, RESOLUTION_PRESETS) so its tips and
197
+ recipe tables never drift from the live app. Fully generic: no user data, no token β€”
198
+ the agent reads HF_TOKEN from its own environment."""
199
+ mode_rows = "\n".join(
200
+ f"| {m} | {_fmt_lr(jobs.MODES[m]['recommended']['learning_rate'])} | "
201
+ f"{jobs.MODES[m]['recommended']['steps']} |"
202
+ for m in MODE_KEYS
203
+ )
204
+
205
+ def _prow(name: str, p: dict) -> str:
206
+ return (f"| {name} | {p['quantization']} | {p['optimizer_type']} | "
207
+ f"{'on' if p['te_8bit'] else 'off'} | {p['rank']} | {p['alpha']} | {p['flavor']} |")
208
+
209
+ profile_rows = "\n".join([
210
+ _prow(PROFILE_QUALITY, PROFILES[PROFILE_QUALITY]),
211
+ _prow(PROFILE_LOWVRAM, PROFILES[PROFILE_LOWVRAM]),
212
+ f"| {PROFILE_CUSTOM} | (you choose) | (you choose) | (you choose) | 32 | 32 | (you choose) |",
213
+ ])
214
+ presets = " Β· ".join(f"`{v}` ({label.split('Β·')[-1].strip()})" for label, v in RESOLUTION_PRESETS)
215
+
216
+ return f"""# Train an LTX-2.3 LoRA for me (ltx-community/ltx2-lora-trainer)
217
+
218
+ You're acting as the trainer Space's UI, but conversational. Walk me through the 4
219
+ steps below **one at a time**. After I answer, apply the suggested defaults from the
220
+ tables (I can override any of them), then submit the job *exactly the way the Space
221
+ does* (Step 4). Confirm my final choices before launching.
222
+
223
+ ## Step 1 β€” Dataset
224
+ {MODE_HELP}
225
+
226
+ - **Dataset source**: a Hub dataset repo (trainer-format `dataset.json`: `media_path`
227
+ + `caption` [+ `reference_video` for IC-LoRA]), OR a local folder of clips I point you to.
228
+ - **Captioning**: drop a `clip.txt` next to each clip for a per-clip caption; otherwise a
229
+ shared caption is used. LTX-2.3 likes long, detailed, *chronological* captions (~200 words:
230
+ motion, camera, lighting, audio). A **trigger word** is prepended to every caption so I can
231
+ invoke the LoRA at inference. For IC-LoRA, every target `X.mp4` needs a paired `X_reference.mp4`.
232
+
233
+ **Apply on mode selection (editable):**
234
+ | mode | learning_rate | steps |
235
+ |---|---|---|
236
+ {mode_rows}
237
+
238
+ ## Step 2 β€” Training (defaults auto-applied from mode + profile β€” tweak freely)
239
+ - **Resolution** `WxHxF` (default `768x512x49`). Rule: W,H divisible by 32; `F % 8 == 1`.
240
+ Presets: {presets}.
241
+ - **Rank/alpha**: keep alpha = rank, range 8–128. **LR/steps** come from the mode table above.
242
+ - **Advanced**: batch size 1 (required for multi-resolution datasets), grad accumulation 1,
243
+ validate every 250 steps (inference uses 30 steps).
244
+
245
+ **Apply on performance-profile selection (editable):**
246
+ | profile | quantization | optimizer | TE 8-bit | rank | alpha | GPU flavor |
247
+ |---|---|---|---|---|---|---|
248
+ {profile_rows}
249
+
250
+ ## Step 3 β€” Launch
251
+ - **Run name**, whether to **push** the LoRA to my Hub, and the **hub model id**.
252
+ - **GPU flavor** (the profile suggests one; change freely):
253
+
254
+ {FLAVOR_GUIDE}
255
+ - **Timeout** (default `6h`). The first run spends ~minutes downloading the model.
256
+
257
+ ## Step 4 β€” Submit & monitor (use the Space's own backend)
258
+ Use MY Hugging Face token from the environment (`hf auth login` or `HF_TOKEN`) β€” never
259
+ hard-code it. Fill `params` with my resolved answers from Steps 1–3, then run:
260
+
261
+ # pip install "huggingface_hub>=1.5" pyyaml hf_xet
262
+ import os, sys, time
263
+ from huggingface_hub import snapshot_download
264
+ sys.path.insert(0, snapshot_download("ltx-community/ltx2-lora-trainer", repo_type="space"))
265
+ import jobs
266
+
267
+ params = {{
268
+ "mode": "<mode from Step 1>",
269
+ "dataset_repo": "<hub dataset, or '' if passing local files below>",
270
+ "run_name": "ltx2-lora",
271
+ "resolution": "768x512x49",
272
+ "rank": 32, "alpha": 32, # from profile table
273
+ "learning_rate": 2e-4, "steps": 3000, # from mode table
274
+ "batch_size": 1, "gradient_accumulation_steps": 1, "validation_interval": 250,
275
+ "quantization": None, "optimizer_type": "adamw", "load_text_encoder_in_8bit": False,
276
+ "push_to_hub": True, "hub_model_id": "USERNAME/my-lora",
277
+ "caption_all": "", "trigger_word": "", "seed": 42,
278
+ "hf_token": os.environ["HF_TOKEN"],
279
+ }}
280
+ # Hub dataset -> [] ; local clips -> ["clip1.mp4", "clip1_reference.mp4", ...]
281
+ res = jobs.submit(params, [], flavor="rtx-pro-6000", timeout="6h")
282
+ job_id = res["job_id"]; print("Submitted:", res["url"] or job_id)
283
+ while True:
284
+ st = jobs.job_status(job_id, params["hf_token"]); print("status:", st)
285
+ if st.upper() in {{"COMPLETED", "ERROR", "CANCELLED", "FAILED"}}: break
286
+ time.sleep(30)
287
+ print(jobs.job_logs(job_id, params["hf_token"])[-4000:])
288
+ """
289
+
290
+
291
+ AGENT_PLAYBOOK = _build_agent_playbook()
292
+
293
+
294
  def _apply_profile(profile: str, rank_dirty: bool, alpha_dirty: bool, flavor_dirty: bool):
295
  """Cascade a profile to (quantization, optimizer, te_8bit, rank, alpha, manual-group visibility,
296
  flavor). rank/alpha/flavor respect dirty flags (preserve manual edits); Custom reveals manual
 
391
  "Train a LoRA / IC-LoRA on your own videos β€” runs on **HF Jobs**, pushed to your Hub.",
392
  elem_id="hero",
393
  )
394
+ with gr.Column(scale=0, min_width=210):
395
+ gr.LoginButton()
396
+ agent_open_btn = gr.Button("πŸ€– Train with your agent", variant="secondary",
397
+ size="sm", elem_id="agent-open-btn")
398
 
399
  banner = gr.Markdown(elem_id="banner")
400
 
401
+ # ── agent hand-off modal ──────────────────────────────────────────────────
402
+ # CSS overlay (see #agent-modal) toggled by the hero button. Uses a Column, not a
403
+ # Group: a Group renders its elem_id on two nested divs and won't re-hide via
404
+ # `visible=False`, whereas a Column toggles cleanly. The snippet is a fully generic
405
+ # playbook built from the same constants the UI uses β€” no user data, no token
406
+ # (the agent reads HF_TOKEN from its own environment).
407
+ with gr.Column(visible=False, elem_id="agent-modal") as agent_modal:
408
+ with gr.Column(elem_id="agent-modal-card"):
409
+ gr.Markdown(
410
+ "### πŸ€– Train with your agent\n"
411
+ "Copy the prompt below into your coding agent. It walks you through the same steps "
412
+ "as this UI, then launches training on **HF Jobs** under your account with your own "
413
+ "Hugging Face token (`hf auth login`) β€” no token or data is embedded here."
414
+ )
415
+ gr.Code(value=AGENT_PLAYBOOK, language="markdown", lines=20, max_lines=22,
416
+ wrap_lines=False, interactive=False, show_label=False, elem_id="agent-snippet")
417
+ agent_close_btn = gr.Button("Close", variant="secondary")
418
+
419
  with gr.Tabs():
420
  # ----------------------------------------------------------------- 1 Β· Dataset
421
  with gr.Tab("1 Β· Dataset"):
 
553
  ).then(_extract_id, inputs=joblink, outputs=job_id)
554
  refresh_btn.click(refresh, inputs=[job_id], outputs=[mon_status, mon_logs])
555
 
556
+ # agent hand-off modal open/close (no inputs β†’ emits the same generic playbook every time)
557
+ agent_open_btn.click(lambda: gr.update(visible=True), outputs=agent_modal)
558
+ agent_close_btn.click(lambda: gr.update(visible=False), outputs=agent_modal)
559
+
560
 
561
  if __name__ == "__main__":
562
  demo.queue(default_concurrency_limit=2).launch(theme=THEME, css=CSS)