Add interactive mutation UI to dashboard
Dashboard now supports all existing mutation APIs: feedback (thumbs up/down, retract), profile CRUD, speaker mapping, playlist generation, track discovery, taste rebuild, and requeue failed embeddings. All controls use vanilla JS fetch with toast notifications. New endpoint: POST /api/admin/requeue-failed resets failed embedding tracks back to pending. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -87,6 +87,7 @@ async def status_page(
|
||||
recent_rows = (await session.execute(listens_query)).all()
|
||||
recent_listens = [
|
||||
{
|
||||
"track_id": track.id,
|
||||
"title": track.title,
|
||||
"artist": track.artist,
|
||||
"speaker": event.speaker_name or "Unknown",
|
||||
@@ -119,6 +120,7 @@ async def status_page(
|
||||
profiles = [
|
||||
{
|
||||
"id": profile.id,
|
||||
"raw_name": profile.name,
|
||||
"name": profile.display_name or profile.name,
|
||||
"event_count": event_count,
|
||||
"track_count": track_count,
|
||||
@@ -193,6 +195,7 @@ async def status_page(
|
||||
feedback_rows = (await session.execute(fb_recent_query)).all()
|
||||
recent_feedback = [
|
||||
{
|
||||
"id": event.id,
|
||||
"signal": event.signal,
|
||||
"signal_weight": event.signal_weight,
|
||||
"title": track.title,
|
||||
@@ -233,6 +236,7 @@ async def status_page(
|
||||
for event, track in influence_rows:
|
||||
if track.id not in tracks_map:
|
||||
tracks_map[track.id] = {
|
||||
"track_id": track.id,
|
||||
"title": track.title,
|
||||
"artist": track.artist,
|
||||
"vibes": [],
|
||||
@@ -268,6 +272,23 @@ async def status_page(
|
||||
# Profile names for selector (use raw Profile.name, not display_name)
|
||||
all_profile_names = [profile.name for profile, *_ in profile_rows]
|
||||
|
||||
# Speaker entities for dropdowns
|
||||
speaker_entities = [
|
||||
("Living Room", "media_player.living_room_speaker_2"),
|
||||
("Dining Room", "media_player.dining_room_speaker_2"),
|
||||
("Basement", "media_player.basement_mini_2"),
|
||||
("Kitchen", "media_player.kitchen_stereo_2"),
|
||||
("Study", "media_player.study_speaker_2"),
|
||||
("Butler's Pantry", "media_player.butlers_pantry_speaker_2"),
|
||||
("Master Bathroom", "media_player.master_bathroom_speaker_2"),
|
||||
("Kids Room", "media_player.kids_room_speaker_2"),
|
||||
("Guest Bedroom", "media_player.guest_bedroom_speaker_2_2"),
|
||||
("Garage", "media_player.garage_wifi_2"),
|
||||
("Whole House", "media_player.whole_house_2"),
|
||||
("Downstairs", "media_player.downstairs_2"),
|
||||
("Upstairs", "media_player.upstairs_2"),
|
||||
]
|
||||
|
||||
template = _jinja_env.get_template("status.html")
|
||||
html = template.render(
|
||||
data=data,
|
||||
@@ -280,6 +301,7 @@ async def status_page(
|
||||
vibe_influence=vibe_influence,
|
||||
selected_profile=selected_profile.name if selected_profile else None,
|
||||
all_profile_names=all_profile_names,
|
||||
speaker_entities=speaker_entities,
|
||||
now=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC"),
|
||||
)
|
||||
return HTMLResponse(html)
|
||||
|
||||
Reference in New Issue
Block a user