feat: enhance discovery, vibe sessions, and library enrichment
Typecheck / typecheck (backend) (push) Has been cancelled
Typecheck / typecheck (workers) (push) Has been cancelled

This commit is contained in:
kami
2026-08-01 14:40:48 +04:00
parent a0c9f42a89
commit 4c48d11e9d
54 changed files with 4136 additions and 521 deletions
+25 -10
View File
@@ -1,4 +1,5 @@
import api from './api';
import axios from 'axios';
import type { Track } from '../types';
// A candidate from the v2 recommendation plan. The plan is stored server-side
@@ -21,6 +22,13 @@ export interface VibeNextResponse {
planRemaining: number;
}
export type VibeBatchStatus = 'complete' | 'exhausted' | 'failed';
export interface VibeBatchResult {
tracks: Track[];
status: VibeBatchStatus;
}
export type VibeFeedbackAction = 'completed' | 'skipped' | 'promoted' | 'disliked';
// V2 recommendation engine. The backend stores the plan in Redis (2h TTL) and
@@ -35,16 +43,16 @@ export const vibeService = {
// 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');
async next(sessionId: string): Promise<VibeNextResponse> {
const res = await api.get<VibeNextResponse>('/v2/vibe/next', { params: { sessionId } });
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 });
async feedback(trackId: string, action: VibeFeedbackAction, sessionId?: string): Promise<{ status: string; planRemaining: number }> {
const res = await api.post<{ status: string; planRemaining: number }>('/v2/vibe/feedback', { trackId, action, sessionId });
return res.data;
},
@@ -58,16 +66,23 @@ export const vibeService = {
// 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[]> {
// 409/VIBE_PLAN_EXHAUSTED is a normal terminal condition. A missing/replaced
// session is intentionally reported as a failure so callers can preserve the
// current playback state rather than pretending the plan completed cleanly.
export async function fetchNextBatch(count: number, sessionId: string): Promise<VibeBatchResult> {
const tracks: Track[] = [];
for (let i = 0; i < count; i++) {
try {
const { track } = await vibeService.next();
const { track } = await vibeService.next(sessionId);
tracks.push(track);
} catch {
break;
} catch (error) {
return {
tracks,
status: axios.isAxiosError(error) && error.response?.status === 409 &&
(error.response.data as { code?: string } | undefined)?.code === 'VIBE_PLAN_EXHAUSTED'
? 'exhausted' : 'failed',
};
}
}
return tracks;
return { tracks, status: 'complete' };
}