tv shows to tv series

This commit is contained in:
2026-04-20 19:37:33 -03:00
parent c319dea2bf
commit 5e02ca267f
20 changed files with 396 additions and 117 deletions

View File

@@ -18,8 +18,8 @@ async function request<T>(path: string, options?: RequestInit): Promise<T> {
export function createRecommendation(body: {
main_prompt: string;
liked_shows: string;
disliked_shows: string;
liked_series: string;
disliked_series: string;
themes: string;
brainstorm_count?: number;
media_type: MediaType;
@@ -68,8 +68,8 @@ export function deleteRecommendation(id: string): Promise<{ ok: boolean }> {
}
export function createContinuousRecommendation(body: {
liked_shows: string;
disliked_shows?: string;
liked_series: string;
disliked_series?: string;
themes?: string;
requirements?: string;
avoid?: string;

View File

@@ -8,8 +8,8 @@ interface NewRecommendationModalProps {
onClose: () => void;
onSubmit: (body: {
main_prompt: string;
liked_shows: string;
disliked_shows: string;
liked_series: string;
disliked_series: string;
themes: string;
requirements?: string;
avoid?: string;
@@ -33,19 +33,19 @@ const MEDIA_OPTIONS: Array<{
label: string;
description: string;
}> = [
{
type: 'tv_show',
icon: '📺',
label: 'TV Shows',
description: 'Serialized stories, limited series, and long-form comfort watches.',
},
{
type: 'movie',
icon: '🎬',
label: 'Movies',
description: 'Feature films, prestige cinema, and one-night picks.',
},
];
{
type: 'tv_show',
icon: '📺',
label: 'TV series',
description: 'Serialized stories, limited series, and long-form comfort watches.',
},
{
type: 'movie',
icon: '🎬',
label: 'Movies',
description: 'Feature films, prestige cinema, and one-night picks.',
},
];
const MODE_OPTIONS: Array<{
mode: GenerationMode;
@@ -53,27 +53,27 @@ const MODE_OPTIONS: Array<{
badge: string;
description: string;
}> = [
{
mode: 'brainstorm',
label: 'Brainstorm',
badge: 'Best for variety',
description: 'Explore a broad pool of options, then rank and curate the strongest fits.',
},
{
mode: 'continuous',
label: 'Continuous',
badge: 'Best for deep search',
description: 'Generate recommendations in chained batches for a steadier, longer-running hunt.',
},
];
{
mode: 'brainstorm',
label: 'Brainstorm',
badge: 'Best for variety',
description: 'Explore a broad pool of options, then rank and curate the strongest fits.',
},
{
mode: 'continuous',
label: 'Continuous',
badge: 'Best for deep search',
description: 'Generate recommendations in chained batches for a steadier, longer-running hunt.',
},
];
export function NewRecommendationModal({ onClose, onSubmit }: NewRecommendationModalProps) {
const [step, setStep] = useState<'type' | 'mode' | 'form'>('type');
const [mediaType, setMediaType] = useState<MediaType>('tv_show');
const [generationMode, setGenerationMode] = useState<GenerationMode>('brainstorm');
const [mainPrompt, setMainPrompt] = useState('');
const [likedShows, setLikedShows] = useState('');
const [dislikedShows, setDislikedShows] = useState('');
const [likedSeries, setLikedSeries] = useState('');
const [dislikedSeries, setDislikedSeries] = useState('');
const [themes, setThemes] = useState('');
const [requirements, setRequirements] = useState('');
const [avoid, setAvoid] = useState('');
@@ -100,7 +100,7 @@ export function NewRecommendationModal({ onClose, onSubmit }: NewRecommendationM
}, [loading, onClose]);
const mediaLabel = mediaType === 'movie' ? 'Movie' : 'TV Show';
const mediaPluralLabel = mediaType === 'movie' ? 'movies' : 'shows';
const mediaPluralLabel = mediaType === 'movie' ? 'movies' : 'series';
const handleSelectType = (type: MediaType) => {
setMediaType(type);
@@ -119,15 +119,15 @@ export function NewRecommendationModal({ onClose, onSubmit }: NewRecommendationM
const handleSubmit = async (e: Event) => {
e.preventDefault();
if (generationMode === 'brainstorm' && !mainPrompt.trim()) return;
if (!likedShows.trim()) return;
if (!likedSeries.trim()) return;
setLoading(true);
try {
if (generationMode === 'brainstorm') {
await onSubmit({
main_prompt: mainPrompt.trim(),
liked_shows: likedShows.trim(),
disliked_shows: dislikedShows.trim(),
liked_series: likedSeries.trim(),
disliked_series: dislikedSeries.trim(),
themes: themes.trim(),
brainstorm_count: brainstormCount,
media_type: mediaType,
@@ -142,8 +142,8 @@ export function NewRecommendationModal({ onClose, onSubmit }: NewRecommendationM
} else {
await onSubmit({
main_prompt: '',
liked_shows: likedShows.trim(),
disliked_shows: dislikedShows.trim(),
liked_series: likedSeries.trim(),
disliked_series: dislikedSeries.trim(),
themes: themes.trim(),
requirements: requirements.trim(),
avoid: avoid.trim(),
@@ -258,7 +258,7 @@ export function NewRecommendationModal({ onClose, onSubmit }: NewRecommendationM
<div class="modal-type-footer">
<div class="modal-selection-summary">
<span class="summary-pill">{mediaType === 'movie' ? 'Movies' : 'TV Shows'}</span>
<span class="summary-pill">{mediaType === 'movie' ? 'Movies' : 'TV series'}</span>
<span class="summary-pill">{selectedMode?.label}</span>
</div>
</div>
@@ -281,7 +281,7 @@ export function NewRecommendationModal({ onClose, onSubmit }: NewRecommendationM
<form class="modal-form" onSubmit={handleSubmit}>
<div class="modal-summary-strip">
<span class="summary-pill">{mediaType === 'movie' ? 'Movies' : 'TV Shows'}</span>
<span class="summary-pill">{mediaType === 'movie' ? 'Movies' : 'TV series'}</span>
<span class="summary-pill summary-pill--accent">{selectedMode?.label}</span>
<span class="summary-caption">
{generationMode === 'brainstorm'
@@ -307,28 +307,28 @@ export function NewRecommendationModal({ onClose, onSubmit }: NewRecommendationM
<div class="modal-form-grid">
<div class="form-group">
<label for="liked-shows">{mediaLabel}s you liked</label>
<label for="liked-series">{mediaLabel}s you liked</label>
<input
id="liked-shows"
id="liked-series"
type="text"
class="form-input"
placeholder={mediaType === 'movie' ? 'e.g. Inception, The Godfather' : 'e.g. Breaking Bad, The Wire'}
value={likedShows}
onInput={(e) => setLikedShows((e.target as HTMLInputElement).value)}
value={likedSeries}
onInput={(e) => setLikedSeries((e.target as HTMLInputElement).value)}
required
/>
<span class="form-help">A few strong examples help the pipeline lock onto your taste.</span>
</div>
<div class="form-group">
<label for="disliked-shows">{mediaLabel}s you disliked</label>
<label for="disliked-series">{mediaLabel}s you disliked</label>
<input
id="disliked-shows"
id="disliked-series"
type="text"
class="form-input"
placeholder={mediaType === 'movie' ? 'e.g. Transformers' : 'e.g. Game of Thrones'}
value={dislikedShows}
onInput={(e) => setDislikedShows((e.target as HTMLInputElement).value)}
value={dislikedSeries}
onInput={(e) => setDislikedSeries((e.target as HTMLInputElement).value)}
/>
<span class="form-help">Optional, but useful when you want to steer away from common misses.</span>
</div>

View File

@@ -14,8 +14,8 @@ type GenerationMode = 'brainstorm' | 'continuous';
interface CreateBody {
main_prompt: string;
liked_shows: string;
disliked_shows: string;
liked_series: string;
disliked_series: string;
themes: string;
requirements?: string;
avoid?: string;
@@ -58,8 +58,8 @@ export function useRecommendations() {
if (body.generation_mode === 'continuous') {
const result = await createContinuousRecommendation({
liked_shows: body.liked_shows,
disliked_shows: body.disliked_shows,
liked_series: body.liked_series,
disliked_series: body.disliked_series,
themes: body.themes,
requirements: body.requirements ?? '',
avoid: body.avoid ?? '',
@@ -73,7 +73,7 @@ export function useRecommendations() {
const result = await createRecommendation(body);
id = result.id;
}
await refreshList();
setSelectedId(id);
return id;

View File

@@ -13,8 +13,8 @@ export function Home() {
const handleCreateNew = async (body: {
main_prompt: string;
liked_shows: string;
disliked_shows: string;
liked_series: string;
disliked_series: string;
themes: string;
requirements?: string;
avoid?: string;

View File

@@ -183,8 +183,8 @@ export function Recom({ id }: RecomProps) {
const handleCreateNew = async (body: {
main_prompt: string;
liked_shows: string;
disliked_shows: string;
liked_series: string;
disliked_series: string;
themes: string;
brainstorm_count?: number;
media_type: import('../types/index.js').MediaType;
@@ -234,16 +234,16 @@ export function Recom({ id }: RecomProps) {
<span class="rec-info-value">{rec.main_prompt}</span>
</div>
)}
{rec.liked_shows && (
{rec.liked_series && (
<div class="rec-info-row">
<span class="rec-info-label">Liked</span>
<span class="rec-info-value">{rec.liked_shows}</span>
<span class="rec-info-value">{rec.liked_series}</span>
</div>
)}
{rec.disliked_shows && (
{rec.disliked_series && (
<div class="rec-info-row">
<span class="rec-info-label">Disliked</span>
<span class="rec-info-value">{rec.disliked_shows}</span>
<span class="rec-info-value">{rec.disliked_series}</span>
</div>
)}
{rec.themes && (
@@ -254,7 +254,7 @@ export function Recom({ id }: RecomProps) {
)}
<div class="rec-info-row">
<span class="rec-info-label">Media</span>
<span class="rec-info-value">{rec.media_type === 'tv_show' ? 'TV Shows' : 'Movies'}</span>
<span class="rec-info-value">{rec.media_type === 'tv_show' ? 'TV series' : 'Movies'}</span>
</div>
{rec.use_web_search && (
<div class="rec-info-row">

View File

@@ -17,8 +17,8 @@ export interface Recommendation {
id: string;
title: string;
main_prompt: string;
liked_shows: string;
disliked_shows: string;
liked_series: string;
disliked_series: string;
themes: string;
media_type: MediaType;
use_web_search: boolean;