techfreakworm commited on
Commit
fdfc10d
·
unverified ·
1 Parent(s): 9c07a74

feat(ui): wire lyrics tab + cross-tab use-in-generate handoff (m4)

Browse files
Files changed (3) hide show
  1. app.py +73 -1
  2. theme.py +53 -0
  3. ui.py +134 -0
app.py CHANGED
@@ -261,6 +261,50 @@ def on_extend_click(
261
  raise gr.Error(str(e)) from e
262
 
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  def on_edit_click(
265
  source_audio,
266
  sub_mode: str,
@@ -513,7 +557,35 @@ def build_app() -> gr.Blocks:
513
  outputs=[e["output_audio"], e["output_meta"]],
514
  )
515
  with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_lyrics:
516
- gr.Markdown("### ✍️ Lyrics\n\nPlaceholder — implemented in M4.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
  panes = [pane_generate, pane_cover, pane_extend, pane_edit, pane_lyrics]
519
 
 
261
  raise gr.Error(str(e)) from e
262
 
263
 
264
+ def on_draft_lyrics(
265
+ brief: str,
266
+ structure: str,
267
+ language: str,
268
+ tone: str,
269
+ verse_lines: float,
270
+ chorus_lines: float,
271
+ bridge_lines: float,
272
+ rhyme: str,
273
+ temperature: float,
274
+ top_p: float,
275
+ top_k: float,
276
+ max_new_tokens: float,
277
+ seed,
278
+ progress=gr.Progress(track_tqdm=True), # noqa: B008
279
+ ):
280
+ """Lyrics-mode click. Calls ``modes.lyrics(...)`` directly — no ACE-Step
281
+ pipeline is touched. Qwen 2.5 7B is its own lazy singleton inside
282
+ ``lyrics_lm``; the first click triggers a ~4 GB MLX download (cached
283
+ afterwards) and ~30 s warm-up before the draft appears.
284
+ """
285
+ try:
286
+ return modes.lyrics(
287
+ get_backend(),
288
+ params={
289
+ "brief": brief,
290
+ "structure": structure,
291
+ "language": language,
292
+ "tone": tone,
293
+ "verse_lines": int(verse_lines),
294
+ "chorus_lines": int(chorus_lines),
295
+ "bridge_lines": int(bridge_lines),
296
+ "rhyme": rhyme,
297
+ "temperature": float(temperature),
298
+ "top_p": float(top_p),
299
+ "top_k": int(top_k),
300
+ "max_new_tokens": int(max_new_tokens),
301
+ "seed": int(seed) if seed is not None else None,
302
+ },
303
+ )
304
+ except ValueError as e:
305
+ raise gr.Error(str(e)) from e
306
+
307
+
308
  def on_edit_click(
309
  source_audio,
310
  sub_mode: str,
 
557
  outputs=[e["output_audio"], e["output_meta"]],
558
  )
559
  with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_lyrics:
560
+ lyr = ui.build_lyrics_tab()
561
+ lyr["draft_btn"].click(
562
+ fn=on_draft_lyrics,
563
+ inputs=[
564
+ lyr["brief"],
565
+ lyr["structure"],
566
+ lyr["language"],
567
+ lyr["tone"],
568
+ lyr["verse_lines"],
569
+ lyr["chorus_lines"],
570
+ lyr["bridge_lines"],
571
+ lyr["rhyme"],
572
+ lyr["temperature"],
573
+ lyr["top_p"],
574
+ lyr["top_k"],
575
+ lyr["max_new_tokens"],
576
+ lyr["seed"],
577
+ ],
578
+ outputs=[lyr["lyrics_output"], lyr["meta_output"]],
579
+ )
580
+ # Cross-tab "Use these in Generate" — pipes the drafted
581
+ # text straight into the Generate tab's lyrics textbox.
582
+ # Both panes were declared inside the same gr.Blocks
583
+ # context so referencing g["lyrics"] across panes works.
584
+ lyr["use_in_generate_btn"].click(
585
+ fn=lambda txt: txt,
586
+ inputs=[lyr["lyrics_output"]],
587
+ outputs=[g["lyrics"]],
588
+ )
589
 
590
  panes = [pane_generate, pane_cover, pane_extend, pane_edit, pane_lyrics]
591
 
theme.py CHANGED
@@ -861,6 +861,59 @@ main, .contain {{
861
  padding:0 12px 12px 12px !important;
862
  }}
863
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
864
  /* Hide Gradio footer + the floating "Use via API" / settings panel */
865
  footer {{ display:none !important; }}
866
  .show-api {{ display:none !important; }}
 
861
  padding:0 12px 12px 12px !important;
862
  }}
863
 
864
+ /* ============================================================
865
+ * Lyrics tab (M4)
866
+ * Mono draft textbox + secondary "Use in Generate" CTA. The LM-params
867
+ * accordion reuses the same chrome as the LoRA + experimental
868
+ * accordions so the bordered section header reads consistently.
869
+ * ============================================================ */
870
+ .ams-content .ams-lyrics-output textarea {{
871
+ font-family: {FONT_MONO} !important;
872
+ font-size: 12px !important;
873
+ line-height: 1.6 !important;
874
+ min-height: 280px !important;
875
+ background:{SURFACE_STRONG} !important;
876
+ border:1px solid {BORDER} !important;
877
+ color:{INK} !important;
878
+ }}
879
+ .ams-content .ams-lyrics-output textarea::placeholder {{
880
+ font-style: italic;
881
+ }}
882
+ .ams-content .ams-lyrics-use-btn {{
883
+ margin-top: 6px !important;
884
+ }}
885
+ .ams-content .ams-lm-accordion {{
886
+ border:1px solid {BORDER} !important;
887
+ border-radius:3px !important;
888
+ background:{SURFACE_STRONG} !important;
889
+ margin-top:10px !important;
890
+ padding:0 !important;
891
+ }}
892
+ .ams-content .ams-lm-accordion > .label-wrap,
893
+ .ams-content .ams-lm-accordion summary,
894
+ .ams-content .ams-lm-accordion > button {{
895
+ font-family: {FONT_MONO} !important;
896
+ font-size:10px !important;
897
+ letter-spacing:0.08em !important;
898
+ text-transform:uppercase !important;
899
+ color:{INK_MUTED} !important;
900
+ padding:10px 12px !important;
901
+ background:transparent !important;
902
+ border:none !important;
903
+ }}
904
+ .ams-content .ams-lm-accordion > .label-wrap span,
905
+ .ams-content .ams-lm-accordion summary span,
906
+ .ams-content .ams-lm-accordion > button span {{
907
+ color:{INK_MUTED} !important;
908
+ font-family: {FONT_MONO} !important;
909
+ font-size:10px !important;
910
+ letter-spacing:0.08em !important;
911
+ text-transform:uppercase !important;
912
+ }}
913
+ .ams-content .ams-lm-accordion > div:not(.label-wrap):not(summary) {{
914
+ padding:0 12px 12px 12px !important;
915
+ }}
916
+
917
  /* Hide Gradio footer + the floating "Use via API" / settings panel */
918
  footer {{ display:none !important; }}
919
  .show-api {{ display:none !important; }}
ui.py CHANGED
@@ -418,3 +418,137 @@ def build_edit_tab() -> dict[str, gr.components.Component]:
418
  _build_output_panel(components)
419
 
420
  return components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  _build_output_panel(components)
419
 
420
  return components
421
+
422
+
423
+ def build_lyrics_tab() -> dict[str, gr.components.Component]:
424
+ """Lyrics tab body: Qwen 2.5 7B drafts structurally-tagged lyrics.
425
+
426
+ Compact 2-column row: form on the left (brief / structure / language /
427
+ line counts / tone / rhyme + collapsed LM-params accordion), output on
428
+ the right (read-only multi-line textbox + ``Use these in Generate``
429
+ cross-tab CTA + bordered JSON metadata panel).
430
+
431
+ The output textbox carries ``elem_classes=["ams-lyrics-output"]`` so
432
+ the Brutalist Mono treatment in ``theme.CSS`` (mono font, 12 px,
433
+ 280 px min-height) applies. The "Use in Generate" button is tagged
434
+ ``ams-lyrics-use-btn`` so it gets a small top margin instead of
435
+ sitting flush against the textbox.
436
+
437
+ Does NOT include the LoRA accordion — Qwen-7B has no LoRA picker and
438
+ the audio-mode LoRA semantics don't apply here.
439
+ """
440
+ c: dict[str, gr.components.Component] = {}
441
+ with gr.Row():
442
+ # --- FORM column (left) ---
443
+ with gr.Column(scale=12):
444
+ c["brief"] = gr.Textbox(
445
+ label="Brief",
446
+ lines=4,
447
+ placeholder=("Describe the song. Tone, mood, references, specific images, lines to avoid…"),
448
+ )
449
+ with gr.Row():
450
+ c["structure"] = gr.Textbox(
451
+ label="Structure",
452
+ value="intro, verse, chorus, verse, chorus, bridge, chorus, outro",
453
+ )
454
+ c["language"] = gr.Dropdown(
455
+ choices=["en", "zh", "ja", "ko", "es", "fr", "de"],
456
+ value="en",
457
+ label="Language",
458
+ )
459
+ with gr.Row():
460
+ c["verse_lines"] = gr.Slider(
461
+ minimum=2,
462
+ maximum=10,
463
+ value=6,
464
+ step=1,
465
+ label="Verse lines",
466
+ )
467
+ c["chorus_lines"] = gr.Slider(
468
+ minimum=2,
469
+ maximum=8,
470
+ value=4,
471
+ step=1,
472
+ label="Chorus lines",
473
+ )
474
+ c["bridge_lines"] = gr.Slider(
475
+ minimum=1,
476
+ maximum=6,
477
+ value=2,
478
+ step=1,
479
+ label="Bridge lines",
480
+ )
481
+ c["tone"] = gr.Textbox(
482
+ label="Tone / mood",
483
+ placeholder="euphoric, hypnotic, transcendent, not cheesy",
484
+ )
485
+ c["rhyme"] = gr.Radio(
486
+ choices=["strict", "loose", "none"],
487
+ value="loose",
488
+ label="Rhyme",
489
+ )
490
+ with gr.Accordion(
491
+ "LM parameters",
492
+ open=False,
493
+ elem_classes=["ams-lm-accordion"],
494
+ ):
495
+ c["temperature"] = gr.Slider(
496
+ minimum=0.0,
497
+ maximum=2.0,
498
+ value=0.85,
499
+ step=0.05,
500
+ label="Temperature",
501
+ )
502
+ c["top_p"] = gr.Slider(
503
+ minimum=0.0,
504
+ maximum=1.0,
505
+ value=0.9,
506
+ step=0.05,
507
+ label="Top-p",
508
+ )
509
+ c["top_k"] = gr.Slider(
510
+ minimum=0,
511
+ maximum=200,
512
+ value=40,
513
+ step=1,
514
+ label="Top-k",
515
+ )
516
+ c["max_new_tokens"] = gr.Slider(
517
+ minimum=100,
518
+ maximum=2000,
519
+ value=600,
520
+ step=50,
521
+ label="Max new tokens",
522
+ )
523
+ c["seed"] = gr.Number(
524
+ value=42,
525
+ precision=0,
526
+ label="Seed",
527
+ )
528
+ c["draft_btn"] = gr.Button(
529
+ "▶ Draft lyrics",
530
+ variant="primary",
531
+ )
532
+
533
+ # --- OUTPUT column (right) ---
534
+ with gr.Column(scale=10):
535
+ # NOTE: gr.Textbox in Gradio 6.14 doesn't accept ``show_copy_button``
536
+ # (the kwarg landed in a later 6.x). The Brutalist Mono textbox already
537
+ # exposes a native selection + browser copy via Cmd-A / Cmd-C; the
538
+ # copy-button affordance is therefore a no-op miss here.
539
+ c["lyrics_output"] = gr.Textbox(
540
+ label="Draft",
541
+ lines=14,
542
+ interactive=False,
543
+ elem_classes=["ams-lyrics-output"],
544
+ )
545
+ c["use_in_generate_btn"] = gr.Button(
546
+ "↑ Use these in Generate",
547
+ variant="primary",
548
+ elem_classes=["ams-lyrics-use-btn"],
549
+ )
550
+ c["meta_output"] = gr.JSON(
551
+ label="Metadata",
552
+ elem_classes=["ams-out", "ams-out-meta"],
553
+ )
554
+ return c