Files
beStream/src/hooks/useRecommendations.ts
kharonsec 766cbbce89
Some checks failed
CI / Lint & Type Check (push) Failing after 43s
CI / Tests (push) Successful in 1m0s
CI / Build Web (push) Has been skipped
CI / Security Scan (push) Successful in 46s
CI / Build Electron (Linux) (push) Has been skipped
CI / Build Tauri (ubuntu-latest) (push) Has been skipped
CI / Build Electron (Windows) (push) Has been cancelled
CI / Build Tauri (windows-latest) (push) Has been cancelled
Add local profiles, smart features, and Google Cast support
- Local Profiles: Profile selector, manager, avatar system, profile-aware stores
- Smart Features: Continue Watching, personalized recommendations, auto-quality, smart downloads
- Google Cast: Cast service with web SDK and Capacitor Android plugin interface
- Settings: New toggles for auto-quality and smart downloads

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 20:44:13 +01:00

174 lines
5.0 KiB
TypeScript

import { useState, useEffect, useMemo } from 'react';
import {
getPersonalizedRecommendations,
getBecauseYouWatched,
} from '../services/recommendations/recommendationService';
import { useHistoryStore } from '../stores/historyStore';
import type { Movie } from '../types';
interface UseRecommendationsResult {
movies: Movie[];
basedOnGenres: string[];
isLoading: boolean;
error: string | null;
}
// Hook for personalized recommendations based on watch history
export function usePersonalizedRecommendations(limit = 20): UseRecommendationsResult {
const [movies, setMovies] = useState<Movie[]>([]);
const [basedOnGenres, setBasedOnGenres] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { getProfileItems } = useHistoryStore();
const historyItems = getProfileItems();
// Memoize history data to prevent unnecessary re-fetches
const historyHash = useMemo(
() => historyItems.map((item) => item.movieId).join(','),
[historyItems]
);
useEffect(() => {
if (historyItems.length === 0) {
setMovies([]);
setBasedOnGenres([]);
setIsLoading(false);
return;
}
const fetchRecommendations = async () => {
setIsLoading(true);
setError(null);
try {
const historyMovies = historyItems.map((item) => item.movie);
const excludeIds = new Set(historyItems.map((item) => item.movieId));
const result = await getPersonalizedRecommendations(
historyMovies,
excludeIds,
limit
);
setMovies(result.movies);
setBasedOnGenres(result.basedOnGenres);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to get recommendations');
} finally {
setIsLoading(false);
}
};
fetchRecommendations();
}, [historyHash, limit]);
return { movies, basedOnGenres, isLoading, error };
}
interface BecauseYouWatchedResult {
sourceMovie: Movie | null;
recommendations: Movie[];
isLoading: boolean;
error: string | null;
}
// Hook for "Because You Watched [Movie]" recommendations
export function useBecauseYouWatched(movieId: number | undefined): BecauseYouWatchedResult {
const [sourceMovie, setSourceMovie] = useState<Movie | null>(null);
const [recommendations, setRecommendations] = useState<Movie[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { getProfileItems } = useHistoryStore();
const historyItems = getProfileItems();
useEffect(() => {
if (!movieId) {
setSourceMovie(null);
setRecommendations([]);
setIsLoading(false);
return;
}
const historyItem = historyItems.find((item) => item.movieId === movieId);
if (!historyItem) {
setSourceMovie(null);
setRecommendations([]);
setIsLoading(false);
return;
}
const fetchRecommendations = async () => {
setIsLoading(true);
setError(null);
try {
const excludeIds = new Set(historyItems.map((item) => item.movieId));
const recs = await getBecauseYouWatched(historyItem.movie, excludeIds, 10);
setSourceMovie(historyItem.movie);
setRecommendations(recs);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to get recommendations');
} finally {
setIsLoading(false);
}
};
fetchRecommendations();
}, [movieId, historyItems]);
return { sourceMovie, recommendations, isLoading, error };
}
// Hook for multiple "Because You Watched" rows
export function useMultipleBecauseYouWatched(maxRows = 3): {
rows: Array<{ sourceMovie: Movie; recommendations: Movie[] }>;
isLoading: boolean;
} {
const [rows, setRows] = useState<Array<{ sourceMovie: Movie; recommendations: Movie[] }>>([]);
const [isLoading, setIsLoading] = useState(true);
const { getProfileItems } = useHistoryStore();
const historyItems = getProfileItems();
useEffect(() => {
if (historyItems.length === 0) {
setRows([]);
setIsLoading(false);
return;
}
const fetchAllRecommendations = async () => {
setIsLoading(true);
try {
const excludeIds = new Set(historyItems.map((item) => item.movieId));
const recentMovies = historyItems.slice(0, maxRows);
const rowPromises = recentMovies.map(async (item) => {
const recs = await getBecauseYouWatched(item.movie, excludeIds, 10);
return {
sourceMovie: item.movie,
recommendations: recs,
};
});
const results = await Promise.all(rowPromises);
// Filter out rows with no recommendations
setRows(results.filter((row) => row.recommendations.length > 0));
} catch (err) {
console.error('Error fetching recommendations:', err);
setRows([]);
} finally {
setIsLoading(false);
}
};
fetchAllRecommendations();
}, [historyItems.length, maxRows]);
return { rows, isLoading };
}