feat(vibe): reconcile mutable session previews in playback
This commit is contained in:
@@ -1,30 +1,34 @@
|
||||
import { create } from 'zustand';
|
||||
import type { Track, VibeSession } from '../types';
|
||||
import type { VibePlanItem } from '../services/vibeService';
|
||||
|
||||
// V2 recommendation session state. The backend stores the plan in Redis
|
||||
// (keyed by sessionId) and serves tracks one at a time via GET /v2/vibe/next.
|
||||
// We keep a lookahead buffer of upcoming Track[] to feed playback.
|
||||
// The durable plan is authoritative. `buffer` is only its currently
|
||||
// uncommitted, hydrated preview; it may be replaced at any feedback boundary.
|
||||
interface VibeState {
|
||||
activeSessionId: string | null;
|
||||
seedTrackId: string | null;
|
||||
planVersion: number | null;
|
||||
/** Durable cursor for the track currently in Vibe playback. */
|
||||
currentPlanItem: VibePlanItem | null;
|
||||
centerTrack: Track | null;
|
||||
buffer: Track[]; // lookahead buffer of upcoming recommended tracks
|
||||
/** Outcome of the first V2 batch, including sessions initiated from Discover. */
|
||||
buffer: Track[];
|
||||
initialBatchStatus: 'idle' | 'loading' | 'exhausted' | 'failed';
|
||||
|
||||
setActiveSession: (session: VibeSession | null) => void;
|
||||
setSeedTrackId: (seedTrackId: string | null) => void;
|
||||
setCenterTrack: (track: Track | null) => void;
|
||||
setBuffer: (buffer: Track[]) => void;
|
||||
/** Returns false when a response belongs to an older plan revision. */
|
||||
setPlan: (planVersion: number | null, preview: Track[]) => boolean;
|
||||
setCurrentPlanItem: (item: VibePlanItem | null) => void;
|
||||
setInitialBatchStatus: (status: VibeState['initialBatchStatus']) => void;
|
||||
appendBuffer: (tracks: Track[]) => void;
|
||||
shiftBuffer: () => Track | undefined;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
activeSessionId: null as string | null,
|
||||
seedTrackId: null as string | null,
|
||||
planVersion: null as number | null,
|
||||
currentPlanItem: null as VibePlanItem | null,
|
||||
centerTrack: null as Track | null,
|
||||
buffer: [] as Track[],
|
||||
initialBatchStatus: 'idle' as const,
|
||||
@@ -33,26 +37,20 @@ const initialState = {
|
||||
export const useVibeStore = create<VibeState>((set, get) => ({
|
||||
...initialState,
|
||||
|
||||
setActiveSession: (session) =>
|
||||
set(
|
||||
session
|
||||
? { activeSessionId: session.sessionId, seedTrackId: session.seedTrackId }
|
||||
: { activeSessionId: null, seedTrackId: null }
|
||||
),
|
||||
|
||||
setActiveSession: (session) => set(
|
||||
session
|
||||
? { activeSessionId: session.sessionId, seedTrackId: session.seedTrackId }
|
||||
: { activeSessionId: null, seedTrackId: null },
|
||||
),
|
||||
setSeedTrackId: (seedTrackId) => set({ seedTrackId }),
|
||||
setCenterTrack: (centerTrack) => set({ centerTrack }),
|
||||
setBuffer: (buffer) => set({ buffer }),
|
||||
setInitialBatchStatus: (initialBatchStatus) => set({ initialBatchStatus }),
|
||||
appendBuffer: (tracks) => set((state) => ({ buffer: [...state.buffer, ...tracks] })),
|
||||
|
||||
shiftBuffer: () => {
|
||||
const { buffer } = get();
|
||||
if (buffer.length === 0) return undefined;
|
||||
const [head, ...rest] = buffer;
|
||||
set({ buffer: rest });
|
||||
return head;
|
||||
setPlan: (planVersion, buffer) => {
|
||||
const current = get().planVersion;
|
||||
if (planVersion === null || (current !== null && planVersion < current)) return false;
|
||||
set({ planVersion, buffer });
|
||||
return true;
|
||||
},
|
||||
|
||||
setCurrentPlanItem: (currentPlanItem) => set({ currentPlanItem }),
|
||||
setInitialBatchStatus: (initialBatchStatus) => set({ initialBatchStatus }),
|
||||
reset: () => set({ ...initialState }),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user