tv shows to tv series
This commit is contained in:
@@ -4,7 +4,7 @@ import { z } from 'zod';
|
||||
import { zodTextFormat } from 'openai/helpers/zod';
|
||||
|
||||
const CuratorSchema = z.object({
|
||||
shows: z.array(z.object({
|
||||
series: z.array(z.object({
|
||||
title: z.string(),
|
||||
explanation: z.string(),
|
||||
category: z.enum(["Full Match", "Definitely Like", "Might Like", "Questionable", "Will Not Like"]),
|
||||
@@ -24,7 +24,7 @@ export async function runCurator(
|
||||
): Promise<CuratorOutput[]> {
|
||||
const mediaLabel = mediaType === 'movie' ? 'movie' : 'TV show';
|
||||
|
||||
const allShows = [
|
||||
const allSeries = [
|
||||
...(ranking.full_match ?? []).map((t) => ({ title: t, category: 'Full Match' as const })),
|
||||
...ranking.definitely_like.map((t) => ({ title: t, category: 'Definitely Like' as const })),
|
||||
...ranking.might_like.map((t) => ({ title: t, category: 'Might Like' as const })),
|
||||
@@ -32,7 +32,7 @@ export async function runCurator(
|
||||
...ranking.will_not_like.map((t) => ({ title: t, category: 'Will Not Like' as const })),
|
||||
];
|
||||
|
||||
if (allShows.length === 0) return [];
|
||||
if (allSeries.length === 0) return [];
|
||||
|
||||
const canSearch = useWebSearch && supportsWebSearch;
|
||||
const preferenceSummary = `User preferences summary:
|
||||
@@ -53,9 +53,9 @@ Rules:
|
||||
- cons: up to 3 short bullet points about what the user might not like based on their preferences
|
||||
- Be honest — explain why "Questionable" or "Will Not Like" ${mediaLabel}s got that rating`;
|
||||
|
||||
const chunks: typeof allShows[] = [];
|
||||
for (let i = 0; i < allShows.length; i += CHUNK_SIZE) {
|
||||
chunks.push(allShows.slice(i, i + CHUNK_SIZE));
|
||||
const chunks: typeof allSeries[] = [];
|
||||
for (let i = 0; i < allSeries.length; i += CHUNK_SIZE) {
|
||||
chunks.push(allSeries.slice(i, i + CHUNK_SIZE));
|
||||
}
|
||||
|
||||
const results: CuratorOutput[] = [];
|
||||
@@ -66,14 +66,14 @@ Rules:
|
||||
temperature: 0.5,
|
||||
...serviceOptions,
|
||||
...(canSearch ? { tools: [{ type: 'web_search' as const }] } : {}),
|
||||
text: { format: zodTextFormat(CuratorSchema, "shows") },
|
||||
text: { format: zodTextFormat(CuratorSchema, "series") },
|
||||
instructions,
|
||||
input: `${preferenceSummary}
|
||||
|
||||
${mediaLabel}s to describe:
|
||||
${showList}`,
|
||||
}));
|
||||
results.push(...(response.output_parsed?.shows ?? []));
|
||||
results.push(...(response.output_parsed?.series ?? []));
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
@@ -15,8 +15,8 @@ const InterpreterSchema = z.object({
|
||||
|
||||
interface InterpreterInput {
|
||||
main_prompt: string;
|
||||
liked_shows: string;
|
||||
disliked_shows: string;
|
||||
liked_series: string;
|
||||
disliked_series: string;
|
||||
themes: string;
|
||||
media_type: MediaType;
|
||||
feedback_context?: string;
|
||||
@@ -43,8 +43,8 @@ Rules:
|
||||
- Be specific and concrete, not vague
|
||||
- For "requirements": capture explicit hard requirements the user stated that recommendations must satisfy — things like "must be from the 2000s onward", "must have subtitles", "must feature a female lead". Leave empty if no such constraints were stated.`,
|
||||
input: `Main prompt: ${input.main_prompt}
|
||||
Liked ${mediaLabel}s: ${input.liked_shows || '(none)'}
|
||||
Disliked ${mediaLabel}s: ${input.disliked_shows || '(none)'}
|
||||
Liked ${mediaLabel}s: ${input.liked_series || '(none)'}
|
||||
Disliked ${mediaLabel}s: ${input.disliked_series || '(none)'}
|
||||
Themes and requirements: ${input.themes || '(none)'}${feedbackSection}`,
|
||||
}));
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export async function runRetrieval(
|
||||
previousFullMatches: string[] = [],
|
||||
): Promise<RetrievalOutput> {
|
||||
const mediaLabel = mediaType === 'movie' ? 'movie' : 'TV show';
|
||||
const mediaLabelPlural = mediaType === 'movie' ? 'movies' : 'TV shows';
|
||||
const mediaLabelPlural = mediaType === 'movie' ? 'movies' : 'TV series';
|
||||
|
||||
const canSearch = useWebSearch && supportsWebSearch;
|
||||
const response = await parseWithRetry(() => openai.responses.parse({
|
||||
|
||||
@@ -38,7 +38,7 @@ function buildSystemPrompt(
|
||||
seenTitles: string[]
|
||||
): string {
|
||||
const mediaLabel = mediaType === 'movie' ? 'movie' : 'TV show';
|
||||
const mediaLabelPlural = mediaType === 'movie' ? 'movies' : 'TV shows';
|
||||
const mediaLabelPlural = mediaType === 'movie' ? 'movies' : 'TV series';
|
||||
|
||||
let prompt = `You are a ${mediaLabel} recommendation specialist. Your task is to recommend titles that match the user's taste profile.
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ export const recommendations = pgTable('recommendations', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
title: text('title').notNull(),
|
||||
main_prompt: text('main_prompt').notNull(),
|
||||
liked_shows: text('liked_shows').notNull().default(''),
|
||||
disliked_shows: text('disliked_shows').notNull().default(''),
|
||||
liked_series: text('liked_series').notNull().default(''),
|
||||
disliked_series: text('disliked_series').notNull().default(''),
|
||||
themes: text('themes').notNull().default(''),
|
||||
brainstorm_count: integer('brainstorm_count').notNull().default(100),
|
||||
media_type: text('media_type').notNull().default('tv_show'),
|
||||
|
||||
@@ -39,8 +39,8 @@ function mergeCuratorOutputs(a: CuratorOutput[], b: CuratorOutput[]): CuratorOut
|
||||
}
|
||||
|
||||
interface ContinuousPipelineInput {
|
||||
likedShows: string;
|
||||
dislikedShows?: string;
|
||||
likedSeries: string;
|
||||
dislikedSeries?: string;
|
||||
themes?: string;
|
||||
requirements?: string;
|
||||
avoid?: string;
|
||||
@@ -57,8 +57,8 @@ export async function runContinuousPipeline(
|
||||
): Promise<CuratorOutput[]> {
|
||||
const startTime = Date.now();
|
||||
const {
|
||||
likedShows,
|
||||
dislikedShows = '',
|
||||
likedSeries,
|
||||
dislikedSeries = '',
|
||||
themes = '',
|
||||
requirements = '',
|
||||
avoid = '',
|
||||
@@ -83,9 +83,9 @@ export async function runContinuousPipeline(
|
||||
const t0 = Date.now();
|
||||
|
||||
interpreterOutput = await runInterpreter({
|
||||
main_prompt: themes || 'recommend shows based on user preferences',
|
||||
liked_shows: likedShows,
|
||||
disliked_shows: dislikedShows,
|
||||
main_prompt: themes || 'recommend series based on user preferences',
|
||||
liked_series: likedSeries,
|
||||
disliked_series: dislikedSeries,
|
||||
themes: themes,
|
||||
media_type: mediaType,
|
||||
});
|
||||
@@ -267,8 +267,8 @@ export async function runContinuousPipeline(
|
||||
.set({
|
||||
title: aiTitle,
|
||||
main_prompt: themes || 'Continuous recommendations',
|
||||
liked_shows: likedShows,
|
||||
disliked_shows: dislikedShows,
|
||||
liked_series: likedSeries,
|
||||
disliked_series: dislikedSeries,
|
||||
themes: themes,
|
||||
brainstorm_count: totalCount,
|
||||
media_type: mediaType,
|
||||
|
||||
@@ -201,8 +201,8 @@ export async function runPipeline(
|
||||
const t0 = Date.now();
|
||||
const interpreterOutput = await runInterpreter({
|
||||
main_prompt: rec.main_prompt,
|
||||
liked_shows: rec.liked_shows,
|
||||
disliked_shows: rec.disliked_shows,
|
||||
liked_series: rec.liked_series,
|
||||
disliked_series: rec.disliked_series,
|
||||
themes: rec.themes,
|
||||
media_type: mediaType,
|
||||
...(feedbackContext !== undefined ? { feedback_context: feedbackContext } : {}),
|
||||
|
||||
@@ -29,8 +29,8 @@ export default async function recommendationsRoute(fastify: FastifyInstance) {
|
||||
fastify.post('/recommendations', async (request, reply) => {
|
||||
const body = request.body as {
|
||||
main_prompt: string;
|
||||
liked_shows?: string;
|
||||
disliked_shows?: string;
|
||||
liked_series?: string;
|
||||
disliked_series?: string;
|
||||
themes?: string;
|
||||
brainstorm_count?: number;
|
||||
media_type?: string;
|
||||
@@ -64,8 +64,8 @@ export default async function recommendationsRoute(fastify: FastifyInstance) {
|
||||
.values({
|
||||
title: title || 'Untitled',
|
||||
main_prompt: body.main_prompt ?? '',
|
||||
liked_shows: body.liked_shows ?? '',
|
||||
disliked_shows: body.disliked_shows ?? '',
|
||||
liked_series: body.liked_series ?? '',
|
||||
disliked_series: body.disliked_series ?? '',
|
||||
themes: body.themes ?? '',
|
||||
brainstorm_count,
|
||||
media_type,
|
||||
@@ -85,8 +85,8 @@ export default async function recommendationsRoute(fastify: FastifyInstance) {
|
||||
// POST /recommendations/continuous — create record and run continuous pipeline with SSE
|
||||
fastify.post('/recommendations/continuous', async (request, reply) => {
|
||||
const body = request.body as {
|
||||
liked_shows: string;
|
||||
disliked_shows?: string;
|
||||
liked_series: string;
|
||||
disliked_series?: string;
|
||||
themes?: string;
|
||||
requirements?: string;
|
||||
avoid?: string;
|
||||
@@ -108,8 +108,8 @@ export default async function recommendationsRoute(fastify: FastifyInstance) {
|
||||
.values({
|
||||
title,
|
||||
main_prompt: body.themes ?? 'Continuous recommendations',
|
||||
liked_shows: body.liked_shows,
|
||||
disliked_shows: body.disliked_shows ?? '',
|
||||
liked_series: body.liked_series,
|
||||
disliked_series: body.disliked_series ?? '',
|
||||
themes: body.themes ?? '',
|
||||
brainstorm_count: totalCount,
|
||||
media_type: mediaType,
|
||||
@@ -155,8 +155,8 @@ export default async function recommendationsRoute(fastify: FastifyInstance) {
|
||||
|
||||
// Run the continuous pipeline (it will now update the existing record)
|
||||
await runContinuousPipeline(recId, {
|
||||
likedShows: body.liked_shows,
|
||||
dislikedShows: body.disliked_shows ?? '',
|
||||
likedSeries: body.liked_series,
|
||||
dislikedSeries: body.disliked_series ?? '',
|
||||
themes: body.themes ?? '',
|
||||
requirements: body.requirements ?? '',
|
||||
avoid: body.avoid ?? '',
|
||||
@@ -305,11 +305,11 @@ export default async function recommendationsRoute(fastify: FastifyInstance) {
|
||||
const feedbackContext =
|
||||
feedbackRows.length > 0
|
||||
? feedbackRows
|
||||
.map(
|
||||
(f) =>
|
||||
`${mediaLabel}: "${f.item_name}" — Rating: ${f.stars}/3 stars${f.feedback ? ` — Comment: ${f.feedback}` : ''}`,
|
||||
)
|
||||
.join('\n')
|
||||
.map(
|
||||
(f) =>
|
||||
`${mediaLabel}: "${f.item_name}" — Rating: ${f.stars}/3 stars${f.feedback ? ` — Comment: ${f.feedback}` : ''}`,
|
||||
)
|
||||
.join('\n')
|
||||
: undefined;
|
||||
|
||||
await runPipeline(rec, (event) => {
|
||||
|
||||
@@ -83,8 +83,8 @@ export interface ContinuousSession {
|
||||
|
||||
export interface ContinuousStartRequest {
|
||||
mediaType: 'tv_show' | 'movie';
|
||||
likedShows: string;
|
||||
dislikedShows?: string;
|
||||
likedSeries: string;
|
||||
dislikedSeries?: string;
|
||||
themes?: string;
|
||||
requirements?: string;
|
||||
avoid?: string;
|
||||
|
||||
Reference in New Issue
Block a user