Files
muzick/frontend/src/services/vibeService.ts
T

102 lines
3.0 KiB
TypeScript

import api from './api';
// These types intentionally mirror the durable session API. Tracks are not
// embedded in a plan revision: the client resolves ids through the normal
// library endpoint so a deleted/hidden track can never become playable merely
// because an older plan mentioned it.
export interface VibePlanItem {
plan_version_id: string;
ordinal: number;
track_id: string;
slot_role: string | null;
candidate_source: string;
score: number;
score_breakdown: Record<string, unknown>;
explanation: unknown;
committed: boolean;
}
export interface DurableVibeSessionResponse {
sessionId: string;
planVersion: number | null;
now: VibePlanItem | null;
preview: VibePlanItem[];
state: Record<string, unknown>;
replanned: boolean;
replanReason: string | null;
}
export type VibeEventType =
| 'playback_started'
| 'progress'
| 'completed'
| 'skipped'
| 'disliked'
| 'kept';
export interface VibeEventInput {
eventId: string;
type: VibeEventType;
trackId?: string;
occurredAt: string;
positionMs?: number;
durationMs?: number;
payload?: Record<string, unknown>;
}
export interface VibeEventResponse extends DurableVibeSessionResponse {
event: { id: string; client_event_id: string | null; type: string };
idempotent: boolean;
}
/** A durable, idempotent advancement past a plan item the player cannot load. */
export interface VibeUnplayableItemInput {
eventId: string;
planVersionId: string;
ordinal: number;
trackId: string;
}
export const vibeService = {
async start(seedTrackId?: string): Promise<DurableVibeSessionResponse> {
const res = await api.post<DurableVibeSessionResponse>('/v2/vibe/sessions', { seedTrackId });
return res.data;
},
async getPlan(sessionId: string, version?: number): Promise<DurableVibeSessionResponse> {
const res = await api.get<DurableVibeSessionResponse>(`/v2/vibe/sessions/${sessionId}/plans`, {
params: version === undefined ? undefined : { version },
});
return res.data;
},
async next(sessionId: string, expectedPlanVersion: number): Promise<DurableVibeSessionResponse> {
const res = await api.post<DurableVibeSessionResponse>(`/v2/vibe/sessions/${sessionId}/next`, {
expectedPlanVersion,
});
return res.data;
},
async advancePastUnplayable(
sessionId: string,
expectedPlanVersion: number,
unplayable: VibeUnplayableItemInput,
): Promise<DurableVibeSessionResponse> {
const res = await api.post<DurableVibeSessionResponse>(`/v2/vibe/sessions/${sessionId}/next`, {
expectedPlanVersion,
unplayable,
});
return res.data;
},
async event(sessionId: string, event: VibeEventInput): Promise<VibeEventResponse> {
const res = await api.post<VibeEventResponse>(`/v2/vibe/sessions/${sessionId}/events`, event);
return res.data;
},
async end(sessionId: string): Promise<DurableVibeSessionResponse> {
const res = await api.post<DurableVibeSessionResponse>(`/v2/vibe/sessions/${sessionId}/end`);
return res.data;
},
};