kalamishere commited on
Commit
e8871cc
Β·
1 Parent(s): c10212b

Derived prompt: fuse user intent + measured arc via write_sa3_prompt_blended

Browse files
Files changed (1) hide show
  1. app.py +73 -24
app.py CHANGED
@@ -174,10 +174,17 @@ def _chain_payload(a: Analysis) -> dict:
174
  }
175
 
176
 
177
- def run_brief(audio_path: str | None, bpm_mode: str, bpm_prior_choice: str, bpm_prior_num: float, model_choice: str, api_key: str = ""):
178
  """Streaming generator β€” yields partial outputs as each stage finishes so
179
  the UI fills in progressively instead of all-at-once after a long wait.
180
 
 
 
 
 
 
 
 
181
  Output tuple (14 slots):
182
  paragraph, caption, wave, data_tbl, sections_tbl, tags_tbl,
183
  errors, timings, sa3_var, sa3_match, clip_plan, mix_chain,
@@ -255,8 +262,27 @@ def run_brief(audio_path: str | None, bpm_mode: str, bpm_prior_choice: str, bpm_
255
  if fut is fut_brief:
256
  paragraph = text
257
  safe = text if not text.startswith("(") else ""
258
- sa3_var_final = outputs.sa3_variation_prompt(a, safe)
259
  sa3_match_final = outputs.sa3_match_style_prompt(a, safe)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  else:
261
  llm_chain = text
262
  mix_chain_final = outputs.mix_chain_text(a, llm_chain)
@@ -922,17 +948,20 @@ def _hint_from_prompt(prompt: str) -> tuple[str, str, float]:
922
 
923
 
924
  def send_tile_to_analysis(tile_id: str | None):
925
- """Bridge from Generate β†’ Analysis-flow upload box. Returns
926
- (audio_path, gen_status_md, bpm_mode, genre, bpm_num) β€” the trailing
927
- three feed the BPM-prior selectors so the analyzer doesn't lock
928
- half-tempo on dnb/jungle SA3 gens. See `_hint_from_prompt`."""
 
 
 
929
  if not tile_id:
930
  return (None, "_❌ Pick a tile from the dropdown first._",
931
- gr.update(), gr.update(), gr.update())
932
  tile = crate.get_tile(tile_id)
933
  if not tile:
934
  return (None, "_❌ Tile not found β€” try Refresh._",
935
- gr.update(), gr.update(), gr.update())
936
  bpm_mode_v, genre_v, bpm_num_v = _hint_from_prompt(tile.source_prompt)
937
  hint_note = ""
938
  if bpm_mode_v == "Manual BPM":
@@ -947,11 +976,16 @@ def send_tile_to_analysis(tile_id: str | None):
947
  gr.update(value=bpm_mode_v),
948
  gr.update(value=genre_v),
949
  gr.update(value=bpm_num_v),
 
950
  )
951
 
952
 
953
  def build_ui() -> gr.Blocks:
954
- with gr.Blocks(title="audioΒ·brief") as demo:
 
 
 
 
955
  # Top bar β€” brand + pollen pill + avatar. Dynamic on connect via the
956
  # wallet handlers below (they output to `topbar` to refresh the pill).
957
  topbar = gr.HTML(_topbar_html())
@@ -976,6 +1010,12 @@ def build_ui() -> gr.Blocks:
976
  # Cleared on disconnect/connect so each visit starts fresh.
977
  session_spend_state = gr.State(0.0)
978
 
 
 
 
 
 
 
979
  # Shared state β€” holds the most recent Analysis + payloads so the
980
  # Compare tab can re-run the same brief through different models.
981
  last_run = gr.State(None)
@@ -1403,10 +1443,15 @@ def build_ui() -> gr.Blocks:
1403
  # bpm_mode/genre/bpm_num get updated from the tile's source prompt
1404
  # before run_brief reads them β€” otherwise dnb gens analyze at half
1405
  # tempo (87 instead of 174 with no prior to anchor librosa).
1406
- outputs=[audio_in, gen_status, bpm_mode, genre, bpm_num],
 
 
 
 
1407
  ).then(
1408
  fn=run_brief,
1409
- inputs=[audio_in, bpm_mode, genre, bpm_num, model_dd, api_key_state],
 
1410
  outputs=[
1411
  paragraph, caption, wave,
1412
  metrics_html,
@@ -1486,23 +1531,27 @@ def build_ui() -> gr.Blocks:
1486
  return demo
1487
 
1488
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1489
  def main() -> int:
 
 
1490
  import os
1491
- demo = build_ui()
1492
- demo.queue(default_concurrency_limit=4)
1493
- # Honour PORT / GRADIO_SERVER_PORT so the harness's autoPort assignment
1494
- # actually takes effect (Gradio does NOT read PORT on its own).
1495
  port_env = os.environ.get("GRADIO_SERVER_PORT") or os.environ.get("PORT")
1496
  port = int(port_env) if port_env else None
1497
- # Allow Gradio to serve audio from the crate dir. Without this, every
1498
- # gen β†’ audio component returns InvalidPathError because /tmp/audio-brief-crate
1499
- # is outside cwd and the platform temp dir.
1500
- demo.launch(
1501
- theme=THEME,
1502
- css=CUSTOM_CSS,
1503
- server_port=port,
1504
- allowed_paths=[str(crate.CRATE_DIR)],
1505
- )
1506
  return 0
1507
 
1508
 
 
174
  }
175
 
176
 
177
+ def run_brief(audio_path: str | None, bpm_mode: str, bpm_prior_choice: str, bpm_prior_num: float, model_choice: str, api_key: str = "", original_prompt: str = ""):
178
  """Streaming generator β€” yields partial outputs as each stage finishes so
179
  the UI fills in progressively instead of all-at-once after a long wait.
180
 
181
+ `original_prompt`: the source SA3 prompt that produced this audio (when
182
+ the tile came from a Generate-then-Use-for-Analysis flow). When set,
183
+ the derived SA3 variation prompt is built via the LLM `write_sa3_prompt_blended`
184
+ call which fuses the user's intent vocabulary with the measured arc β€”
185
+ so variants stay in the user's stylistic lane instead of drifting toward
186
+ whatever the LLM thought it heard. Empty string for uploaded files.
187
+
188
  Output tuple (14 slots):
189
  paragraph, caption, wave, data_tbl, sections_tbl, tags_tbl,
190
  errors, timings, sa3_var, sa3_match, clip_plan, mix_chain,
 
262
  if fut is fut_brief:
263
  paragraph = text
264
  safe = text if not text.startswith("(") else ""
 
265
  sa3_match_final = outputs.sa3_match_style_prompt(a, safe)
266
+ # Derived prompt β€” fuse user intent + measured arc when
267
+ # we have an original prompt (i.e. the user routed in
268
+ # via Generate β†’ Use for analysis). Falls back to the
269
+ # structural-only template for uploaded files OR if the
270
+ # blend call itself fails.
271
+ sa3_var_final = outputs.sa3_variation_prompt(a, safe)
272
+ if original_prompt and safe:
273
+ try:
274
+ blended, _ = write_sa3_prompt_blended(
275
+ original_prompt=original_prompt,
276
+ brief_payload=_brief_payload(a),
277
+ brief_text=safe,
278
+ model=model_choice,
279
+ api_key=api_key,
280
+ )
281
+ if blended and not blended.startswith("("):
282
+ sa3_var_final = blended
283
+ except Exception as e:
284
+ print(f"[run_brief] blend failed: {type(e).__name__}: {e}",
285
+ file=sys.stderr, flush=True)
286
  else:
287
  llm_chain = text
288
  mix_chain_final = outputs.mix_chain_text(a, llm_chain)
 
948
 
949
 
950
  def send_tile_to_analysis(tile_id: str | None):
951
+ """Bridge from Generate β†’ Analysis-flow upload box.
952
+
953
+ Returns: (audio_path, status_md, bpm_mode, genre, bpm_num, source_prompt)
954
+ The trailing three selectors feed the BPM-prior so the analyzer doesn't
955
+ lock half-tempo on dnb/jungle SA3 gens. `source_prompt` is stashed in a
956
+ gr.State so run_brief can blend it back into the derived SA3 prompt β€”
957
+ without this the variants drift away from the user's original intent."""
958
  if not tile_id:
959
  return (None, "_❌ Pick a tile from the dropdown first._",
960
+ gr.update(), gr.update(), gr.update(), "")
961
  tile = crate.get_tile(tile_id)
962
  if not tile:
963
  return (None, "_❌ Tile not found β€” try Refresh._",
964
+ gr.update(), gr.update(), gr.update(), "")
965
  bpm_mode_v, genre_v, bpm_num_v = _hint_from_prompt(tile.source_prompt)
966
  hint_note = ""
967
  if bpm_mode_v == "Manual BPM":
 
976
  gr.update(value=bpm_mode_v),
977
  gr.update(value=genre_v),
978
  gr.update(value=bpm_num_v),
979
+ tile.source_prompt,
980
  )
981
 
982
 
983
  def build_ui() -> gr.Blocks:
984
+ # Theme + CSS must live on the Blocks instance (not on .launch()) so
985
+ # HF Spaces β€” which auto-launches `demo` without our launch args β€”
986
+ # still picks them up. Gradio v6 supports both call sites; we just
987
+ # have to pick the one that works in both deploy paths.
988
+ with gr.Blocks(title="audioΒ·brief", theme=THEME, css=CUSTOM_CSS) as demo:
989
  # Top bar β€” brand + pollen pill + avatar. Dynamic on connect via the
990
  # wallet handlers below (they output to `topbar` to refresh the pill).
991
  topbar = gr.HTML(_topbar_html())
 
1010
  # Cleared on disconnect/connect so each visit starts fresh.
1011
  session_spend_state = gr.State(0.0)
1012
 
1013
+ # The SA3 prompt that produced the currently-analyzed tile. Set by
1014
+ # send_tile_to_analysis from tile.source_prompt; threaded into
1015
+ # run_brief so the LLM can fuse user intent with measured arc.
1016
+ # Empty string for tiles uploaded from disk (no source prompt known).
1017
+ current_source_prompt = gr.State("")
1018
+
1019
  # Shared state β€” holds the most recent Analysis + payloads so the
1020
  # Compare tab can re-run the same brief through different models.
1021
  last_run = gr.State(None)
 
1443
  # bpm_mode/genre/bpm_num get updated from the tile's source prompt
1444
  # before run_brief reads them β€” otherwise dnb gens analyze at half
1445
  # tempo (87 instead of 174 with no prior to anchor librosa).
1446
+ # current_source_prompt carries the tile's source_prompt into
1447
+ # run_brief so the LLM can blend user-intent back into the
1448
+ # derived SA3 prompt.
1449
+ outputs=[audio_in, gen_status, bpm_mode, genre, bpm_num,
1450
+ current_source_prompt],
1451
  ).then(
1452
  fn=run_brief,
1453
+ inputs=[audio_in, bpm_mode, genre, bpm_num, model_dd,
1454
+ api_key_state, current_source_prompt],
1455
  outputs=[
1456
  paragraph, caption, wave,
1457
  metrics_html,
 
1531
  return demo
1532
 
1533
 
1534
+ # Allow Gradio to serve files from the crate dir. On HF Spaces this MUST be
1535
+ # called at module load β€” Spaces ignores main()/demo.launch() and grabs the
1536
+ # module-level `demo` symbol directly, so any allowed_paths passed to
1537
+ # .launch() would never be applied. set_static_paths is the global hook.
1538
+ gr.set_static_paths([str(crate.CRATE_DIR)])
1539
+
1540
+ # Build the UI at module load and expose `demo` so HF can pick it up. The
1541
+ # build is fast (a few hundred ms) and only runs once per worker. On HF
1542
+ # Spaces, Gradio's auto-launch calls .queue() and .launch() itself.
1543
+ demo = build_ui()
1544
+ demo.queue(default_concurrency_limit=4)
1545
+
1546
+
1547
  def main() -> int:
1548
+ """Local entrypoint β€” `python app.py`. On HF Spaces this never runs;
1549
+ Spaces imports the module and launches `demo` directly."""
1550
  import os
 
 
 
 
1551
  port_env = os.environ.get("GRADIO_SERVER_PORT") or os.environ.get("PORT")
1552
  port = int(port_env) if port_env else None
1553
+ # Theme + CSS already live on the Blocks instance (set in build_ui).
1554
+ demo.launch(server_port=port)
 
 
 
 
 
 
 
1555
  return 0
1556
 
1557