diff --git a/src/haunt_fm/api/status_page.py b/src/haunt_fm/api/status_page.py index 3b67022..e513f4d 100644 --- a/src/haunt_fm/api/status_page.py +++ b/src/haunt_fm/api/status_page.py @@ -92,6 +92,7 @@ async def status_page( "artist": track.artist, "speaker": event.speaker_name or "Unknown", "listened_at": event.listened_at, + "profile_id": event.profile_id, } for event, track in recent_rows ] @@ -111,6 +112,13 @@ async def status_page( ) ).all() + # Map profile_id to name for recent listens + profile_name_by_id: dict[int, str] = { + profile.id: profile.name for profile, *_ in profile_rows + } + for listen in recent_listens: + listen["profile_name"] = profile_name_by_id.get(listen.pop("profile_id"), "default") + # Speaker mappings keyed by profile_id mapping_rows = (await session.execute(select(SpeakerProfileMapping))).scalars().all() speakers_by_profile: dict[int, list[str]] = {} diff --git a/src/haunt_fm/templates/status.html b/src/haunt_fm/templates/status.html index 11dc1c3..72a1a97 100644 --- a/src/haunt_fm/templates/status.html +++ b/src/haunt_fm/templates/status.html @@ -283,6 +283,17 @@ {% endif %} + +
+
+ Vibe context + +
+
+ Feedback is contextual — describes what mood this feedback applies to +
+
+

Recent Listens

@@ -292,11 +303,11 @@
{{ listen.title }} — {{ listen.artist }} -
{{ listen.speaker }} · {{ listen.listened_at | timeago }}
+
{{ listen.speaker }} · {{ listen.profile_name }} · #{{ listen.track_id }} · {{ listen.listened_at | timeago }}
- - + +
{% endfor %} @@ -410,8 +421,8 @@
- - + +
{% endfor %} @@ -424,6 +435,20 @@

Actions

+
+ Feedback + + + + +
Discover @@ -671,20 +696,51 @@ } // --- Feedback --- - async function submitFeedback(btn, trackId, signal) { + function getVibeContext() { + return document.getElementById('vibe-context').value.trim(); + } + + async function submitFeedback(btn, trackId, signal, profileName) { + const vibe = getVibeContext(); + if (!vibe) { + showToast('Set a vibe context first', 'error'); + return; + } + const profile = profileName || CURRENT_PROFILE; btn.disabled = true; - const res = await apiCall('POST', '/api/profiles/' + CURRENT_PROFILE + '/feedback', { - track_id: trackId, signal: signal, vibe: 'general' + const res = await apiCall('POST', '/api/profiles/' + profile + '/feedback', { + track_id: trackId, signal: signal, vibe: vibe }); if (res.ok) { btn.classList.add('sent'); - showToast('Feedback recorded: ' + signal, 'success'); + showToast('Feedback: ' + signal + ' on #' + trackId + ' → ' + profile + ' (' + vibe + ')', 'success'); } else { btn.disabled = false; showToast('Feedback failed: ' + res.error, 'error'); } } + async function submitManualFeedback() { + const trackId = parseInt(document.getElementById('manual-fb-track').value); + const signal = document.getElementById('manual-fb-signal').value; + const profile = document.getElementById('manual-fb-profile').value; + const vibe = getVibeContext(); + if (!trackId) { showToast('Enter a track ID', 'error'); return; } + if (!vibe) { showToast('Set a vibe context first', 'error'); return; } + const btn = event.target; + setLoading(btn, true); + const res = await apiCall('POST', '/api/profiles/' + profile + '/feedback', { + track_id: trackId, signal: signal, vibe: vibe + }); + setLoading(btn, false); + if (res.ok) { + showToast('Feedback: ' + signal + ' on #' + trackId + ' → ' + profile + ' (' + vibe + ')', 'success'); + document.getElementById('manual-fb-track').value = ''; + } else { + showToast('Feedback failed: ' + res.error, 'error'); + } + } + async function retractFeedback(btn, eventId) { btn.disabled = true; const res = await apiCall('DELETE', '/api/feedback/' + eventId);