v0.6.0: form limits, callback expiry, dashboard filter, payment ref, STEMS interest, helper refactor

This commit is contained in:
Troll (Hermes Agent) 2026-08-09 05:18:40 +00:00
parent 0bfe8c315c
commit 0a01381d44
9 changed files with 603 additions and 497 deletions

View file

@ -131,7 +131,7 @@
<div class="filters">
<a href="{{ url_for('admin_dashboard') }}" class="{% if current_status in (None, '') %}active{% endif %}">All</a>
<a href="{{ url_for('admin_dashboard', status='pending') }}" class="{% if current_status == 'pending' %}active{% endif %}">Pending</a>
<a href="{{ url_for('admin_dashboard', status='songs_uploaded') }}" class="{% if current_status == 'songs_uploaded' %}active{% endif %}">Needs Upload</a>
<a href="{{ url_for('admin_dashboard', status='prompt_ready') }}" class="{% if current_status == 'prompt_ready' %}active{% endif %}">Needs Upload</a>
<a href="{{ url_for('admin_dashboard', status='awaiting_payment') }}" class="{% if current_status == 'awaiting_payment' %}active{% endif %}">Awaiting Payment</a>
<a href="{{ url_for('admin_dashboard', status='delivered') }}" class="{% if current_status == 'delivered' %}active{% endif %}">Delivered</a>
<span class="refresh-hint">

View file

@ -245,7 +245,12 @@
</select>
<label for="customer_extra_requests">Anything else</label>
<textarea id="customer_extra_requests" name="extra_requests" rows="3">{{ req.extra_requests or '' }}</textarea>
<textarea id="customer_extra_requests" name="extra_requests" rows="3" maxlength="2000">{{ req.extra_requests or '' }}</textarea>
<label for="customer_stems_interest" style="display:flex;align-items:center;gap:.5rem;margin-top:1rem;cursor:pointer;">
<input type="checkbox" id="customer_stems_interest" name="stems_interest" value="1" {% if req.stems_interest %}checked{% endif %} style="width:auto;margin:0">
Customer is interested in STEMS / multitrack files
</label>
<div class="actions">
<button type="submit" class="secondary">Save Customer Info</button>
@ -433,6 +438,17 @@
{% if req.square_payment_ref %}
<p><strong>Square Payment Reference:</strong> {{ req.square_payment_ref }}</p>
{% endif %}
<form method="POST">
<input type="hidden" name="action" value="update_payment_ref">
<label for="square_payment_ref_edit">Square Payment Reference</label>
<input type="text" id="square_payment_ref_edit" name="square_payment_ref" value="{{ req.square_payment_ref or '' }}" placeholder="e.g. sq0idp-... or receipt number">
<div class="actions">
<button type="submit" class="secondary">Update Payment Reference</button>
</div>
</form>
<hr style="border-color:#374151;margin:1rem 0">
<form method="POST">
<input type="hidden" name="action" value="mark_paid_deliver">
@ -460,8 +476,8 @@
<p>No files available. Upload songs first.</p>
{% endfor %}
<label for="square_payment_ref">Square Payment Reference</label>
<input type="text" id="square_payment_ref" name="square_payment_ref" placeholder="e.g. sq0idp-... or receipt number"
<label for="square_payment_ref">Square Payment Reference (required to deliver)</label>
<input type="text" id="square_payment_ref" name="square_payment_ref" value="{{ req.square_payment_ref or '' }}" placeholder="e.g. sq0idp-... or receipt number"
{% if req.customer_approved == 'none' %}disabled{% endif %}>
<div class="actions">
<button type="submit" class="success" {% if req.customer_approved == 'none' %}disabled{% endif %}>Mark Paid &amp; Deliver</button>
@ -492,9 +508,10 @@
notable_facts: {{ req.notable_facts | tojson }},
style_genre: styleGenre,
vocal_gender: {{ req.vocal_gender | tojson }},
extra_requests: {{ req.extra_requests | tojson }}
extra_requests: {{ req.extra_requests | tojson }},
stems_interest: {{ (req.stems_interest or 0) | tojson }}
};
const text = `Booth request: ${data.request_id}\\nCustomer email: ${data.email}\\nCallback URL: ${callbackUrl}\\n\\nGenerate a Suno Custom Mode prompt for this customer and POST it back to the Callback URL as JSON.\\n\\nCustomer data:\\nName: ${data.name}\\nPronouns: ${data.pronouns || '-'}\\nHobbies: ${data.hobbies || '-'}\\nNotable facts: ${data.notable_facts || '-'}\\nStyle / genre: ${styleSentence || styleGenre || '-'}\\nPreferred singer voice / gender: ${data.vocal_gender || 'No preference'}\\nExtra requests: ${data.extra_requests || '-'}\\n\\nExpected JSON response format:\\n{\\n \\"request_id\\": ${data.request_id},\\n \\"email\\": \\"${data.email}\\",\\n \\"suno_title\\": \\"...\\",\\n \\"suno_style\\": \\"...\\",\\n \\"suno_lyrics\\": \\"...\\"\\n}`;
const text = `Booth request: ${data.request_id}\\nCustomer email: ${data.email}\\nCallback URL: ${callbackUrl}\\n\\nGenerate a Suno Custom Mode prompt for this customer and POST it back to the Callback URL as JSON.\\n\\nCustomer data:\\nName: ${data.name}\\nPronouns: ${data.pronouns || '-'}\\nHobbies: ${data.hobbies || '-'}\\nNotable facts: ${data.notable_facts || '-'}\\nStyle / genre: ${styleSentence || styleGenre || '-'}\\nPreferred singer voice / gender: ${data.vocal_gender || 'No preference'}\\nExtra requests: ${data.extra_requests || '-'}\\nInterested in STEMS: ${data.stems_interest ? 'Yes' : 'No'}\\n\\nExpected JSON response format:\\n{\\n \\"request_id\\": ${data.request_id},\\n \\"email\\": \\"${data.email}\\",\\n \\"suno_title\\": \\"...\\",\\n \\"suno_style\\": \\"...\\",\\n \\"suno_lyrics\\": \\"...\\"\\n}`;
navigator.clipboard.writeText(text).then(() => alert("Copied! Paste into Hermes. Hermes will POST the generated prompt back to the Callback URL."));
}
@ -527,9 +544,10 @@
notable_facts: {{ req.notable_facts | tojson }},
style_genre: styleGenre,
vocal_gender: {{ req.vocal_gender | tojson }},
extra_requests: {{ req.extra_requests | tojson }}
extra_requests: {{ req.extra_requests | tojson }},
stems_interest: {{ (req.stems_interest or 0) | tojson }}
};
const text = `Booth request: ${data.request_id}\\nCustomer email: ${data.email}\\nCallback URL: ${callbackUrl}\\n\\nThis is REVISION #${revisionCount || 1} for this customer.\\n\\nGenerate a NEW Suno Custom Mode prompt that addresses the following revision request, while keeping the same overall theme/persona and matching the customer's original brief as closely as possible.\\n\\nOriginal brief:\\nName: ${data.name}\\nPronouns: ${data.pronouns || '-'}\\nHobbies: ${data.hobbies || '-'}\\nNotable facts: ${data.notable_facts || '-'}\\nStyle / genre: ${styleSentence || styleGenre || '-'}\\nPreferred singer voice / gender: ${data.vocal_gender || 'No preference'}\\nExtra requests: ${data.extra_requests || '-'}\\n\\nPreviously generated prompt (do not copy verbatim; adapt and improve):\\nTitle: ${currentTitle || '-'}\\nStyle: ${currentStyle || '-'}\\nLyrics:\\n${currentLyrics || '-'}\\n\\nRevision request from customer:\\n${revisionNote || '-'}\\n\\nPOST the new prompt back to the Callback URL as JSON with this shape:\\n{\\n \\"request_id\\": ${data.request_id},\\n \\"email\\": \\"${data.email}\\",\\n \\"suno_title\\": \\"...\\",\\n \\"suno_style\\": \\"...\\",\\n \\"suno_lyrics\\": \\"...\\"\\n}`;
const text = `Booth request: ${data.request_id}\\nCustomer email: ${data.email}\\nCallback URL: ${callbackUrl}\\n\\nThis is REVISION #${revisionCount || 1} for this customer.\\n\\nGenerate a NEW Suno Custom Mode prompt that addresses the following revision request, while keeping the same overall theme/persona and matching the customer's original brief as closely as possible.\\n\\nOriginal brief:\\nName: ${data.name}\\nPronouns: ${data.pronouns || '-'}\\nHobbies: ${data.hobbies || '-'}\\nNotable facts: ${data.notable_facts || '-'}\\nStyle / genre: ${styleSentence || styleGenre || '-'}\\nPreferred singer voice / gender: ${data.vocal_gender || 'No preference'}\\nExtra requests: ${data.extra_requests || '-'}\\nInterested in STEMS: ${data.stems_interest ? 'Yes' : 'No'}\\n\\nPreviously generated prompt (do not copy verbatim; adapt and improve):\\nTitle: ${currentTitle || '-'}\\nStyle: ${currentStyle || '-'}\\nLyrics:\\n${currentLyrics || '-'}\\n\\nRevision request from customer:\\n${revisionNote || '-'}\\n\\nPOST the new prompt back to the Callback URL as JSON with this shape:\\n{\\n \\"request_id\\": ${data.request_id},\\n \\"email\\": \\"${data.email}\\",\\n \\"suno_title\\": \\"...\\",\\n \\"suno_style\\": \\"...\\",\\n \\"suno_lyrics\\": \\"...\\"\\n}`;
navigator.clipboard.writeText(text).then(() => alert("Revision prompt copied! Paste into Hermes. Hermes will POST the revised prompt back to the Callback URL."));
}
</script>

View file

@ -291,6 +291,17 @@
{% if not hermes_key_set %}
<p class="status-bad">⚠️ No Hermes API key is configured. Set the HERMES_API_KEY environment variable in Portainer before using the callback workflow.</p>
{% endif %}
<hr style="border:none;border-top:1px solid var(--border);margin:1.5rem 0;">
<h3>Callback Link Expiry</h3>
<p class="copy-hint">How long the signed Hermes callback URL stays valid (in hours). Default is 168 hours (7 days).</p>
<form method="POST">
<input type="hidden" name="action" value="save_callback_expiry">
<label for="callback_expiry_hours">Callback expiry (hours)</label>
<input type="number" id="callback_expiry_hours" name="callback_expiry_hours" value="{{ current_callback_expiry_hours }}" min="1">
<button type="submit">Save Callback Expiry</button>
</form>
</div>
<!-- ntfy push notifications -->