feat(app): show generation progress and rename app heading

- Disable the run button and show a "Генерация…" status while a request is
  processing, then re-enable it. Repeated generations previously gave no
  visible feedback once the model was cached, so it was unclear whether a
  request was running.
- Append elapsed time to the final status so identical re-runs differ visibly.
- Rename the H1 heading and browser/tab title to
  "генератор аккордовых последовательностей".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 21:00:29 +03:00
parent c56397df54
commit d1af7bceb8
+36 -4
View File
@@ -22,6 +22,7 @@ import tempfile
from dataclasses import replace from dataclasses import replace
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
from time import perf_counter
from uuid import uuid4 from uuid import uuid4
import gradio as gr import gradio as gr
@@ -106,6 +107,7 @@ def generate(
tempo: int, tempo: int,
): ):
"""Run one generation and return (status, bar grid, .chord path, .mid path).""" """Run one generation and return (status, bar grid, .chord path, .mid path)."""
t0 = perf_counter()
try: try:
model = _load_model(checkpoint) model = _load_model(checkpoint)
except Exception as exc: # noqa: BLE001 — surface any load error to the UI except Exception as exc: # noqa: BLE001 — surface any load error to the UI
@@ -160,15 +162,37 @@ def generate(
write_chord_file(period, chord_path) write_chord_file(period, chord_path)
chord_file_to_midi(chord_path, midi_path, tempo=int(tempo)) chord_file_to_midi(chord_path, midi_path, tempo=int(tempo))
elapsed = perf_counter() - t0
status = ( status = (
f"✅ Готово — {len(period.bars)} тактов · {target_key} · " f"✅ Готово — {len(period.bars)} тактов · {target_key} · "
f"модель: {checkpoint} · seed: {seed_val if seed_val is not None else 'random'}" f"модель: {checkpoint} · seed: {seed_val if seed_val is not None else 'random'} · "
f"{elapsed:.2f} с"
) )
if truncated: if truncated:
status += f" · обрезано до {MAX_PERIOD_BARS} тактов (период ≤ 16)" status += f" · обрезано до {MAX_PERIOD_BARS} тактов (период ≤ 16)"
return status, _format_bars(period), str(chord_path), str(midi_path) return status, _format_bars(period), str(chord_path), str(midi_path)
# ---------------------------------------------------------------------------
# Button-state helpers — give immediate, persistent feedback while generating.
# Gradio's built-in spinner barely flashes once the model is cached, so we also
# disable the button and show a "generating" status until the work completes.
# ---------------------------------------------------------------------------
def _begin_generation():
"""Show a busy state the instant the button is clicked (runs before generate)."""
return (
gr.update(value="⏳ Генерация…", interactive=False), # run button
"⏳ Генерация…", # status message
"", # clear the previous grid
)
def _end_generation():
"""Restore the run button after generation finishes (success or handled error)."""
return gr.update(value="Сгенерировать", interactive=True)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Russian instructions (rendered inline) # Russian instructions (rendered inline)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -220,9 +244,9 @@ def build_ui() -> gr.Blocks:
checkpoints[0] if checkpoints else "finetuned" checkpoints[0] if checkpoints else "finetuned"
) )
with gr.Blocks(title="hamori — генератор гармонии") as demo: with gr.Blocks(title="hamori — генератор аккордовых последовательностей") as demo:
gr.Markdown( gr.Markdown(
"# hamori 🎶 — генератор гармонических периодов\n" "# hamori 🎶 — генератор аккордовых последовательностей\n"
"Заполните форму и нажмите **Сгенерировать**. " "Заполните форму и нажмите **Сгенерировать**. "
"Подробности — в разделе «Инструкция» внизу." "Подробности — в разделе «Инструкция» внизу."
) )
@@ -255,7 +279,7 @@ def build_ui() -> gr.Blocks:
subdivision = gr.Radio([4, 8], value=4, label="Subdivision") subdivision = gr.Radio([4, 8], value=4, label="Subdivision")
with gr.Row(): with gr.Row():
auto_bars = gr.Checkbox(value=False, label="Авто (длина сама)") auto_bars = gr.Checkbox(value=False, label="Авто")
n_bars = gr.Slider(4, 16, value=8, step=1, label="Число тактов") n_bars = gr.Slider(4, 16, value=8, step=1, label="Число тактов")
with gr.Accordion("Сэмплирование", open=True): with gr.Accordion("Сэмплирование", open=True):
@@ -290,6 +314,10 @@ def build_ui() -> gr.Blocks:
gr.Markdown(INSTRUCTIONS_RU) gr.Markdown(INSTRUCTIONS_RU)
run.click( run.click(
fn=_begin_generation,
inputs=None,
outputs=[run, status, bars_out],
).then(
fn=generate, fn=generate,
inputs=[ inputs=[
checkpoint, mode, key, style, function, time, subdivision, checkpoint, mode, key, style, function, time, subdivision,
@@ -297,6 +325,10 @@ def build_ui() -> gr.Blocks:
tonic_anchor, prefix_text, seed, tempo, tonic_anchor, prefix_text, seed, tempo,
], ],
outputs=[status, bars_out, chord_file, midi_file], outputs=[status, bars_out, chord_file, midi_file],
).then(
fn=_end_generation,
inputs=None,
outputs=[run],
) )
return demo return demo