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);