ymlin105's picture
feat: enhance recommendation system with improved routing, latency optimizations, and onboarding features
52a0642
const API_URL = import.meta.env.VITE_API_URL || (import.meta.env.PROD ? "" : "http://127.0.0.1:6006");
export async function recommend(query, category = "All", tone = "All", user_id = "local", use_agentic = false, fast = false, async_rerank = false) {
const body = { query, category, tone, user_id, use_agentic, fast, async_rerank };
const resp = await fetch(`${API_URL}/recommend`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!resp.ok) throw new Error(await resp.text());
const data = await resp.json();
return data.recommendations || [];
}
export async function getOnboardingBooks(limit = 24) {
const resp = await fetch(`${API_URL}/api/onboarding/books?limit=${limit}`);
if (!resp.ok) throw new Error(await resp.text());
const data = await resp.json();
return data.books || [];
}
export async function getPersonalizedRecommendations(user_id = "local", limit = 20, recent_isbns = null, intent_query = null) {
// P1: recent_isbns — session-level ISBNs for cold-start (1+ clicks)
// P2: intent_query — zero-shot intent probing when user has no history
const params = new URLSearchParams({ user_id, limit: limit.toString() });
if (recent_isbns && Array.isArray(recent_isbns) && recent_isbns.length > 0) {
params.set("recent_isbns", recent_isbns.join(","));
}
if (intent_query && typeof intent_query === "string" && intent_query.trim()) {
params.set("intent_query", intent_query.trim());
}
const resp = await fetch(`${API_URL}/api/recommend/personal?${params.toString()}`);
if (!resp.ok) throw new Error(await resp.text());
const data = await resp.json();
return data.recommendations || [];
}
export async function getSimilarBooks(isbn, k = 6, category = "All") {
const params = new URLSearchParams({ k: k.toString(), category });
const resp = await fetch(`${API_URL}/api/recommend/similar/${encodeURIComponent(isbn)}?${params.toString()}`);
if (!resp.ok) throw new Error(await resp.text());
const data = await resp.json();
return data.recommendations || [];
}
export async function addFavorite(isbn, userId = "local") {
const resp = await fetch(`${API_URL}/favorites/add`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ isbn, user_id: userId }),
});
if (!resp.ok) throw new Error(await resp.text());
return resp.json();
}
export async function getPersona(userId = "local") {
const resp = await fetch(`${API_URL}/user/${userId}/persona`);
if (!resp.ok) throw new Error(await resp.text());
return resp.json();
}
export async function getFavorites(userId = "local") {
const resp = await fetch(`${API_URL}/favorites/list/${userId}`);
if (!resp.ok) throw new Error(await resp.text());
const data = await resp.json();
return data.favorites || [];
}
export async function updateBook(isbn, updates, userId = "local") {
const resp = await fetch(`${API_URL}/favorites/update`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ isbn, user_id: userId, ...updates }),
});
if (!resp.ok) throw new Error(await resp.text());
return resp.json();
}
export async function removeFromFavorites(isbn, userId = "local") {
const resp = await fetch(`${API_URL}/favorites/remove`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ isbn, user_id: userId }),
});
if (!resp.ok) throw new Error(await resp.text());
return resp.json();
}
export async function getUserStats(userId = "local") {
const resp = await fetch(`${API_URL}/user/${userId}/stats`);
if (!resp.ok) throw new Error(await resp.text());
return resp.json();
}
export async function getHighlights(isbn, userId = "local") {
const resp = await fetch(`${API_URL}/marketing/highlights`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ isbn, user_id: userId }),
});
if (!resp.ok) throw new Error(await resp.text());
return resp.json();
}
export async function streamChat({ isbn, query, apiKey, provider, onChunk, onError }) {
try {
const resp = await fetch(`${API_URL}/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-LLM-Key": apiKey || ""
},
body: JSON.stringify({
isbn,
query,
user_id: "local",
provider: provider || "ollama"
}),
});
if (!resp.ok) {
const errText = await resp.text();
throw new Error(errText || resp.statusText);
}
const reader = resp.body.getReader();
const decoder = new TextDecoder("utf-8");
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
if (chunk) onChunk(chunk);
}
} catch (e) {
if (onError) onError(e);
else console.error(e);
}
}
export async function addBook(bookData) {
const resp = await fetch(`${API_URL}/books/add`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(bookData),
});
if (!resp.ok) throw new Error(await resp.text());
return resp.json();
}
export async function searchGoogleBooks(query) {
const resp = await fetch(`https://www.googleapis.com/books/v1/volumes?q=${encodeURIComponent(query)}&maxResults=5`);
if (!resp.ok) throw new Error("Failed to search Google Books");
const data = await resp.json();
return data.items || [];
}