Replace free-text genre field with structured style dropdowns

The customer request form previously had an open 'Style / genre / mood'
text field, which produced inconsistent genre descriptions and confused
Suno. Replace it with three dropdowns:

- Decade / era (required) — loaded from /mnt/Storage/Decades.txt
- Basic style (required) — loaded from /mnt/Storage/Music Genres.txt
- Additional style (optional) — same genre list

The combined value is stored in the existing style_genre column as a
comma-separated string (e.g. '1980's, Pop, Funk'), so no schema change
is required. The admin request page exposes the same dropdowns for
corrections, and the copy-to-Hermes prompt formats the style as a clean
sentence like '1980's-era Pop with Funk influences'.

The canonical lists live in /mnt/Storage; bundled copies under lists/
act as a fallback when that mount is unavailable in the container.

Bumps version to 0.4.6 and updates README.
This commit is contained in:
Troll (Hermes Agent) 2026-08-05 17:03:28 +00:00
parent d79c58691a
commit 10ee182558
7 changed files with 381 additions and 16 deletions

View file

@ -146,8 +146,48 @@
<label for="notable_facts">Notable things about you</label>
<textarea id="notable_facts" name="notable_facts" placeholder="Anything fun, weird, or heroic we should mention">{{ form.notable_facts if form else '' }}</textarea>
<label for="style_genre">Style / genre / mood</label>
<input type="text" id="style_genre" name="style_genre" value="{{ form.style_genre if form else '' }}" placeholder="e.g. 80s power ballad, cinematic orchestral, lo-fi synthwave">
<label for="decade">Decade / era <span style="color:#f87171">*</span></label>
<select id="decade" name="decade" required>
<option value="" {% if not form or not form.decade %}selected{% endif %}>— Select decade —</option>
{% for d in decades %}
<option value="{{ d }}" {% if form and form.decade == d %}selected{% endif %}>{{ d }}</option>
{% endfor %}
</select>
<label for="basic_style">Basic style <span style="color:#f87171">*</span></label>
<select id="basic_style" name="basic_style" required>
<option value="" {% if not form or not form.basic_style %}selected{% endif %}>— Select style —</option>
{% for g in genres %}
<option value="{{ g }}" {% if form and form.basic_style == g %}selected{% endif %}>{{ g }}</option>
{% endfor %}
</select>
<label for="additional_style">Additional style (optional)</label>
<select id="additional_style" name="additional_style">
<option value="" {% if not form or not form.additional_style %}selected{% endif %}>— None —</option>
{% for g in genres %}
<option value="{{ g }}" {% if form and form.additional_style == g %}selected{% endif %}>{{ g }}</option>
{% endfor %}
</select>
<p style="font-size:.85rem;color:#9ca3af;margin-top:.3rem">Selected style: <span id="style-preview">{% if form %}{{ form.decade or '' }}{% if form.decade and form.basic_style %}, {% endif %}{{ form.basic_style or '' }}{% if form.basic_style and form.additional_style %}, {% endif %}{{ form.additional_style or '' }}{% else %}—{% endif %}</span></p>
<script>
// Live preview of the combined style string as the customer changes dropdowns.
(function(){
const decade = document.getElementById('decade');
const basic = document.getElementById('basic_style');
const additional = document.getElementById('additional_style');
const preview = document.getElementById('style-preview');
function updatePreview(){
const parts = [decade.value, basic.value, additional.value].filter(Boolean);
preview.textContent = parts.length ? parts.join(', ') : '—';
}
decade.addEventListener('change', updatePreview);
basic.addEventListener('change', updatePreview);
additional.addEventListener('change', updatePreview);
})();
</script>
<label for="vocal_gender">Preferred singer voice / gender</label>
<select id="vocal_gender" name="vocal_gender">