discovery: add Settings -> Discovery -> Adventurousness slider + align config key
A 0..1 range slider (Safe <-> Adventurous) in Settings -> Discovery with a live value readout. It auto-saves via the settings page existing range-input listener, persists to config under discover.adventurousness (matching the /api/settings "discover" section), and re-populates on load. The listening-recs route reads that key (fixed from discovery. -> discover. to match the settings section). Drag it up and "Based On Your Listening" leans harder toward obscure picks on the next load. 64 script-integrity tests green.
This commit is contained in:
parent
ce8faf0ecc
commit
29f84b5601
3 changed files with 39 additions and 1 deletions
|
|
@ -29608,7 +29608,7 @@ def get_discover_listening_recommendations():
|
||||||
# don't carry it inline, so this works on existing data), then push globally-popular picks
|
# don't carry it inline, so this works on existing data), then push globally-popular picks
|
||||||
# down per the user's dial. level 0 -> unchanged. Fail-soft: any hiccup leaves the order.
|
# down per the user's dial. level 0 -> unchanged. Fail-soft: any hiccup leaves the order.
|
||||||
try:
|
try:
|
||||||
level = float(config_manager.get('discovery.adventurousness', 0.3) or 0)
|
level = float(config_manager.get('discover.adventurousness', 0.3) or 0)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
level = 0.0
|
level = 0.0
|
||||||
if level > 0 and stored:
|
if level > 0 and stored:
|
||||||
|
|
|
||||||
|
|
@ -6201,6 +6201,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end Listening Stats body -->
|
</div><!-- end Listening Stats body -->
|
||||||
|
|
||||||
|
<!-- Discovery (collapsible tile) -->
|
||||||
|
<div class="settings-section-header collapsed" data-stg="library" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
|
||||||
|
<span class="settings-section-arrow">▼</span>
|
||||||
|
<h3>Discovery</h3>
|
||||||
|
<span class="settings-section-hint">How adventurous your recommendations are</span>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section-body collapsed" data-stg="library" style="display:none">
|
||||||
|
<div class="settings-group" data-stg="library">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Adventurousness</label>
|
||||||
|
<div style="display:flex; align-items:center; gap:12px; margin-top:8px">
|
||||||
|
<span style="font-size:12px; color:#999">Safe</span>
|
||||||
|
<input type="range" id="discover-adventurousness" min="0" max="1" step="0.05" value="0.3" style="flex:1"
|
||||||
|
oninput="var v=document.getElementById('discover-adventurousness-val'); if(v) v.textContent=parseFloat(this.value).toFixed(2)">
|
||||||
|
<span style="font-size:12px; color:#999">Adventurous</span>
|
||||||
|
<span id="discover-adventurousness-val" style="min-width:36px; text-align:right; font-weight:700; color:var(--accent)">0.30</span>
|
||||||
|
</div>
|
||||||
|
<div class="help-text setting-help-body" style="margin-top:10px">
|
||||||
|
Higher pushes globally-popular artists down so more obscure, non-obvious picks surface; lower keeps recommendations safe and close to what you already know. Affects <strong>Based On Your Listening</strong>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><!-- end Discovery body -->
|
||||||
|
|
||||||
<!-- Security & Access (collapsible tile) -->
|
<!-- Security & Access (collapsible tile) -->
|
||||||
<div class="settings-section-header collapsed" data-stg="advanced" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
|
<div class="settings-section-header collapsed" data-stg="advanced" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
|
||||||
<span class="settings-section-arrow">▼</span>
|
<span class="settings-section-arrow">▼</span>
|
||||||
|
|
|
||||||
|
|
@ -1480,6 +1480,13 @@ async function loadSettingsData() {
|
||||||
// Populate Listening Stats settings
|
// Populate Listening Stats settings
|
||||||
document.getElementById('listening-stats-enabled').checked = settings.listening_stats?.enabled === true;
|
document.getElementById('listening-stats-enabled').checked = settings.listening_stats?.enabled === true;
|
||||||
document.getElementById('listening-stats-interval').value = settings.listening_stats?.poll_interval || 30;
|
document.getElementById('listening-stats-interval').value = settings.listening_stats?.poll_interval || 30;
|
||||||
|
const _advEl = document.getElementById('discover-adventurousness');
|
||||||
|
if (_advEl) {
|
||||||
|
const _adv = settings.discover?.adventurousness;
|
||||||
|
_advEl.value = (typeof _adv === 'number') ? _adv : 0.3;
|
||||||
|
const _advVal = document.getElementById('discover-adventurousness-val');
|
||||||
|
if (_advVal) _advVal.textContent = parseFloat(_advEl.value).toFixed(2);
|
||||||
|
}
|
||||||
document.getElementById('lossy-copy-options').style.display =
|
document.getElementById('lossy-copy-options').style.display =
|
||||||
settings.lossy_copy?.enabled ? 'block' : 'none';
|
settings.lossy_copy?.enabled ? 'block' : 'none';
|
||||||
|
|
||||||
|
|
@ -3495,6 +3502,13 @@ async function saveSettings(quiet = false) {
|
||||||
enabled: document.getElementById('listening-stats-enabled').checked,
|
enabled: document.getElementById('listening-stats-enabled').checked,
|
||||||
poll_interval: parseInt(document.getElementById('listening-stats-interval').value) || 30,
|
poll_interval: parseInt(document.getElementById('listening-stats-interval').value) || 30,
|
||||||
},
|
},
|
||||||
|
discover: {
|
||||||
|
// Adventurousness dial (0 safe .. 1 obscure) — drives the Discover popularity penalty.
|
||||||
|
// Use the slider's value directly (0 is valid; don't `|| 0.3` it away).
|
||||||
|
adventurousness: document.getElementById('discover-adventurousness')
|
||||||
|
? parseFloat(document.getElementById('discover-adventurousness').value)
|
||||||
|
: 0.3,
|
||||||
|
},
|
||||||
m3u_export: {
|
m3u_export: {
|
||||||
enabled: document.getElementById('m3u-export-enabled').checked,
|
enabled: document.getElementById('m3u-export-enabled').checked,
|
||||||
entry_base_path: document.getElementById('m3u-entry-base-path').value || ''
|
entry_base_path: document.getElementById('m3u-entry-base-path').value || ''
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue