feat: add per-song MP3 download buttons to admin request page
Adds an admin download route (/admin/request/<rid>/download-song/<version>) and Download buttons next to Version A and Version B in section 2 (Songs), shown only when the MP3 exists on disk. Bumps version to 0.9.2.
This commit is contained in:
parent
530451cfef
commit
be85a88543
5 changed files with 34 additions and 3 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Theme Song Booth (MusicGPT Edition)
|
||||
|
||||

|
||||

|
||||
|
||||
A Flask web application for running a convention booth where visitors request a custom AI-generated theme song. Operators manage the queue from an admin dashboard, generate prompts via Hermes, queue generations through the MusicGPT API, and deliver final songs by email.
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ The selected pronouns are stored in the `pronouns` column and included in confir
|
|||
|------|------|---------|
|
||||
| Login | `/admin/login` | Simple session-based login. Password comes from the `ADMIN_PASSWORD` environment variable. |
|
||||
| Dashboard | `/admin` | Main queue. Filter by status and auto-refresh at a configurable interval. Includes a **Refresh MusicGPT Status** button to manually poll all in-flight tasks. |
|
||||
| Request detail | `/admin/request/<id>` | Full control of one request: edit customer info, save prompt, copy Hermes callback, queue MusicGPT generation, poll a single MusicGPT task, view status/cost, view revision history, upload MP3 overrides, send preview, record payment, deliver files (MP3/WAV/cover), add operator notes, cancel generation, and generate STEMS. |
|
||||
| Request detail | `/admin/request/<id>` | Full control of one request: edit customer info, save prompt, copy Hermes callback, queue MusicGPT generation, poll a single MusicGPT task, view status/cost, view revision history, upload MP3 overrides, download individual Version A/B MP3s, send preview, record payment, deliver files (MP3/WAV/cover), add operator notes, cancel generation, and generate STEMS. |
|
||||
| Pricing | `/admin/pricing` | Configure fixed prices and up to 5 custom items. Also shows running MusicGPT cost totals in USD. |
|
||||
| Sales | `/admin/sales` | Report of all delivered requests with customer details and Square payment references. |
|
||||
| Settings | `/admin/settings` | Database health, backups, SMTP config, MP3 metadata defaults, revision limit, auto-refresh interval, kiosk mode, booth open/closed switch, Hermes API key management, MusicGPT webhook URL, album cover delivery toggle, automatic MusicGPT polling toggle, and system reset. |
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.9.1
|
||||
0.9.2
|
||||
|
|
|
|||
18
app.py
18
app.py
|
|
@ -922,6 +922,24 @@ def admin_download_stems(rid):
|
|||
return send_file(buf, mimetype='application/zip', as_attachment=True, download_name=download_name)
|
||||
|
||||
|
||||
@app.route('/admin/request/<int:rid>/download-song/<version>')
|
||||
def admin_download_song(rid, version):
|
||||
"""Download a single song MP3 (Version A or B) for a request."""
|
||||
redir = require_admin()
|
||||
if redir:
|
||||
return redir
|
||||
req = get_request_by_id(rid)
|
||||
if not req:
|
||||
abort(404)
|
||||
if version not in ('a', 'b'):
|
||||
abort(404)
|
||||
path = req.get('song_a_path') if version == 'a' else req.get('song_b_path')
|
||||
if not path or not Path(path).exists():
|
||||
flash('Song file not found.', 'error')
|
||||
return redirect(url_for('admin_request', rid=rid))
|
||||
return send_file(path, as_attachment=True, download_name=Path(path).name)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Admin routes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -558,6 +558,13 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.booth-page .song-download {
|
||||
flex: 0 0 auto;
|
||||
align-self: center;
|
||||
font-size: 0.85rem;
|
||||
padding: var(--space-1) var(--space-3);
|
||||
}
|
||||
|
||||
.booth-page .file-select {
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
|
|
|
|||
|
|
@ -313,6 +313,9 @@
|
|||
<span class="status-badge missing">Not ready</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% if req.song_a_path and file_exists(req.song_a_path) %}
|
||||
<a href="{{ url_for('admin_download_song', rid=req.id, version='a') }}" class="button-link song-download">⬇ Download</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="song-row">
|
||||
<input type="checkbox" id="delete_b" name="delete_song" value="b" {% if not (req.song_b_path and file_exists(req.song_b_path)) %}disabled{% endif %}>
|
||||
|
|
@ -326,6 +329,9 @@
|
|||
<span class="status-badge missing">Not ready</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% if req.song_b_path and file_exists(req.song_b_path) %}
|
||||
<a href="{{ url_for('admin_download_song', rid=req.id, version='b') }}" class="button-link song-download">⬇ Download</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit" class="button-danger" onclick="return confirm('Delete selected song files? This will reset the request to Prompt Ready.')">Delete Selected Songs</button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue