initial state: muzick music player + recommendation engine

This commit is contained in:
kami
2026-07-14 01:35:52 +04:00
commit 737bf19fd1
196 changed files with 32431 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
import api from './api';
import type { Track } from '../types';
// A candidate from the v2 recommendation plan. The plan is stored server-side
// in Redis; the frontend only needs trackId + explanation for display.
export interface VibePlanItem {
trackId: string;
generatorId: string;
explanation: unknown[];
relevance: number;
}
export interface VibeStartResponse {
sessionId: string;
plan: VibePlanItem[];
}
export interface VibeNextResponse {
track: Track;
explanation: unknown[] | null;
planRemaining: number;
}
export type VibeFeedbackAction = 'completed' | 'skipped' | 'promoted' | 'disliked';
// V2 recommendation engine. The backend stores the plan in Redis (2h TTL) and
// serves tracks one at a time via GET /next. Feedback triggers replanning.
export const vibeService = {
// POST /api/v2/vibe/start { seedTrackId? } -> { sessionId, plan }
async start(seedTrackId?: string): Promise<VibeStartResponse> {
const res = await api.post<VibeStartResponse>('/v2/vibe/start', { seedTrackId });
return res.data;
},
// GET /api/v2/vibe/next -> { track, explanation, planRemaining }
// Returns one track at a time, shifting the server-side plan.
// 404 if no active plan — caller should handle gracefully.
async next(): Promise<VibeNextResponse> {
const res = await api.get<VibeNextResponse>('/v2/vibe/next');
return res.data;
},
// POST /api/v2/vibe/feedback { trackId, action } -> { status, planRemaining }
// Action 'promoted' also calls addFavorite; 'disliked' also calls dislikeTrack.
// Triggers replan of the remaining plan.
async feedback(trackId: string, action: VibeFeedbackAction): Promise<{ status: string; planRemaining: number }> {
const res = await api.post<{ status: string; planRemaining: number }>('/v2/vibe/feedback', { trackId, action });
return res.data;
},
// GET /api/v2/vibe/plan -> { sessionId, planRemaining, plan }
// Debug endpoint — returns the full remaining plan.
async getPlan(): Promise<{ sessionId: string; planRemaining: number; plan: VibePlanItem[] }> {
const res = await api.get('/v2/vibe/plan');
return res.data;
},
};
// Fetch N tracks from the v2 plan sequentially. Each call to /next shifts the
// server-side plan, so calls must be sequential (not parallel). Stops early on
// 404 (plan exhausted or expired).
export async function fetchNextBatch(count: number): Promise<Track[]> {
const tracks: Track[] = [];
for (let i = 0; i < count; i++) {
try {
const { track } = await vibeService.next();
tracks.push(track);
} catch {
break;
}
}
return tracks;
}