Spaces:
Sleeping
Sleeping
Commit Β·
e8871cc
1
Parent(s): c10212b
Derived prompt: fuse user intent + measured arc via write_sa3_prompt_blended
Browse files
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.
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1407 |
).then(
|
| 1408 |
fn=run_brief,
|
| 1409 |
-
inputs=[audio_in, bpm_mode, genre, bpm_num, model_dd,
|
|
|
|
| 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 |
-
#
|
| 1498 |
-
|
| 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 |
|