import type { MediaType, Recommendation, RecommendationSummary, FeedbackEntry } from '../types/index.js'; const BASE = '/api'; async function request(path: string, options?: RequestInit): Promise { const res = await fetch(`${BASE}${path}`, { headers: { ...(options?.body ? { 'Content-Type': 'application/json' } : {}), }, ...options, }); if (!res.ok) { const text = await res.text(); throw new Error(`HTTP ${res.status}: ${text}`); } return res.json() as Promise; } export function createRecommendation(body: { main_prompt: string; liked_series: string; disliked_series: string; themes: string; brainstorm_count?: number; media_type: MediaType; use_web_search?: boolean; use_validator?: boolean; hard_requirements?: boolean; self_expansive?: boolean; expansive_passes?: number; expansive_mode?: 'soft' | 'extreme'; }): Promise<{ id: string }> { return request('/recommendations', { method: 'POST', body: JSON.stringify(body), }); } export function listRecommendations(): Promise { return request('/recommendations'); } export function getRecommendation(id: string): Promise { return request(`/recommendations/${id}`); } export function rerankRecommendation(id: string): Promise<{ ok: boolean }> { return request(`/recommendations/${id}/rerank`, { method: 'POST' }); } export function submitFeedback(body: { item_name: string; stars: number; feedback?: string; }): Promise<{ ok: boolean }> { return request('/feedback', { method: 'POST', body: JSON.stringify(body), }); } export function getFeedback(): Promise { return request('/feedback'); } export function deleteRecommendation(id: string): Promise<{ ok: boolean }> { return request(`/recommendations/${id}`, { method: 'DELETE' }); } export function createContinuousRecommendation(body: { liked_series: string; disliked_series?: string; themes?: string; requirements?: string; avoid?: string; total_count: number; media_type: MediaType; use_web_search?: boolean; validate_results?: boolean; }): Promise<{ id: string }> { return (async () => { const res = await fetch(`${BASE}/recommendations/continuous`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream', }, body: JSON.stringify(body), }); if (!res.ok) { const text = await res.text(); throw new Error(`HTTP ${res.status}: ${text}`); } if (!res.body) { throw new Error('Missing response stream for continuous recommendation'); } const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { value, done } = await reader.read(); if (value) { buffer += decoder.decode(value, { stream: !done }); } let boundary = buffer.indexOf('\n\n'); while (boundary !== -1) { const rawEvent = buffer.slice(0, boundary).trim(); buffer = buffer.slice(boundary + 2); if (rawEvent) { const dataLine = rawEvent .split('\n') .find((line) => line.startsWith('data:')); if (dataLine) { const payload = dataLine.slice(5).trim(); const event = JSON.parse(payload) as { type?: string; id?: string }; if (event.type === 'created' && event.id) { await reader.cancel(); return { id: event.id }; } } } boundary = buffer.indexOf('\n\n'); } if (done) { break; } } } finally { reader.releaseLock(); } throw new Error('Continuous recommendation stream ended before returning an id'); })(); }