78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { hybridAI } from '@/lib/ai/hybridRouter';
|
|
import {
|
|
getLegacyArticlesBySection,
|
|
getLegacyArticleBySlug,
|
|
} from '@/lib/articles/legacy-source';
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const { interests, readingHistory } = await request.json();
|
|
|
|
// Pull a recent slice of news for the AI to choose from. Cap at 100 so the
|
|
// prompt stays bounded.
|
|
const news = await getLegacyArticlesBySection('news', 100);
|
|
|
|
const articleIndex = news.map((a) => ({
|
|
slug: a.slug,
|
|
title: a.title,
|
|
excerpt: a.excerpt,
|
|
tags: a.tags,
|
|
category: a.category,
|
|
date: a.date,
|
|
}));
|
|
|
|
const systemPrompt = `You are a personalized content recommendation engine for AV Beat, a pro AV / live production / display tech news platform.
|
|
Given a reader's topic interests and reading history, select the 4 most relevant articles from the provided article list.
|
|
Return ONLY a valid JSON array of slugs in order of relevance. Example: ["slug-1","slug-2","slug-3","slug-4"]
|
|
Do not include any explanation or markdown — only the raw JSON array.`;
|
|
|
|
const userPrompt = `Reader interests: ${interests?.length ? interests.join(', ') : 'general pro AV and live production'}
|
|
Reading history (recently read slugs): ${readingHistory?.length ? readingHistory.join(', ') : 'none'}
|
|
|
|
Available articles:
|
|
${JSON.stringify(articleIndex, null, 2)}
|
|
|
|
Return the 4 most relevant article slugs as a JSON array.`;
|
|
|
|
const result = await hybridAI(
|
|
[
|
|
{ role: 'system', content: systemPrompt },
|
|
{ role: 'user', content: userPrompt },
|
|
],
|
|
{ maxTokens: 200, temperature: 0.3, priority: 4 }
|
|
);
|
|
|
|
const content = result.text;
|
|
|
|
let slugs: string[] = [];
|
|
try {
|
|
const match = content.match(/\[[\s\S]*?\]/);
|
|
slugs = match ? JSON.parse(match[0]) : JSON.parse(content.trim());
|
|
} catch {
|
|
const matches = content.match(/"([^"]+)"/g);
|
|
slugs = matches ? matches.map((m: string) => m.replace(/"/g, '')).slice(0, 4) : [];
|
|
}
|
|
|
|
// Resolve from the candidate set first; if a slug wasn't in the recent
|
|
// window, look it up via DB so AI hallucinations or older slugs still resolve.
|
|
const suggested = (
|
|
await Promise.all(
|
|
slugs.slice(0, 4).map(async (slug: string) =>
|
|
news.find((a) => a.slug === slug) || (await getLegacyArticleBySlug(slug))
|
|
)
|
|
)
|
|
).filter(Boolean);
|
|
|
|
if (suggested.length === 0) {
|
|
return NextResponse.json({ suggestions: news.slice(0, 4) });
|
|
}
|
|
|
|
return NextResponse.json({ suggestions: suggested });
|
|
} catch (error) {
|
|
console.error('Article suggestions error:', error);
|
|
const fallback = await getLegacyArticlesBySection('news', 4);
|
|
return NextResponse.json({ suggestions: fallback });
|
|
}
|
|
}
|