Fix: Add batch processing to prevent timeout on large song libraries

This commit is contained in:
Hördle Bot
2025-11-22 12:11:11 +01:00
parent 15746f404a
commit 10cab22cfe
2 changed files with 85 additions and 24 deletions

View File

@@ -134,19 +134,56 @@ export default function AdminPage() {
setCategorizationResults(null); setCategorizationResults(null);
try { try {
let offset = 0;
let hasMore = true;
let allResults: any[] = [];
let totalUncategorized = 0;
let totalProcessed = 0;
// Process in batches
while (hasMore) {
const res = await fetch('/api/categorize', { const res = await fetch('/api/categorize', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ offset })
}); });
if (res.ok) { if (!res.ok) {
const data = await res.json();
setCategorizationResults(data);
fetchSongs(); // Refresh song list
fetchGenres(); // Refresh genre counts
} else {
const error = await res.json(); const error = await res.json();
alert(`Categorization failed: ${error.error || 'Unknown error'}`); alert(`Categorization failed: ${error.error || 'Unknown error'}`);
break;
} }
const data = await res.json();
totalUncategorized = data.totalUncategorized;
totalProcessed = data.processed;
hasMore = data.hasMore;
offset = data.nextOffset || 0;
// Accumulate results
allResults = [...allResults, ...data.results];
// Update UI with progress
setCategorizationResults({
message: `Processing: ${totalProcessed} / ${totalUncategorized} songs...`,
totalProcessed: totalUncategorized,
totalCategorized: allResults.length,
results: allResults,
inProgress: hasMore
});
}
// Final update
setCategorizationResults({
message: `Completed! Processed ${totalUncategorized} songs, categorized ${allResults.length}`,
totalProcessed: totalUncategorized,
totalCategorized: allResults.length,
results: allResults,
inProgress: false
});
fetchSongs(); // Refresh song list
fetchGenres(); // Refresh genre counts
} catch (error) { } catch (error) {
alert('Failed to categorize songs'); alert('Failed to categorize songs');
console.error(error); console.error(error);

View File

@@ -6,6 +6,7 @@ const prisma = new PrismaClient();
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY; const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
const OPENROUTER_MODEL = 'anthropic/claude-3.5-haiku'; const OPENROUTER_MODEL = 'anthropic/claude-3.5-haiku';
const BATCH_SIZE = 20; // Process 20 songs at a time to avoid timeouts
interface CategorizeResult { interface CategorizeResult {
songId: number; songId: number;
@@ -14,7 +15,7 @@ interface CategorizeResult {
assignedGenres: string[]; assignedGenres: string[];
} }
export async function POST() { export async function POST(request: Request) {
try { try {
if (!OPENROUTER_API_KEY) { if (!OPENROUTER_API_KEY) {
return Response.json( return Response.json(
@@ -23,7 +24,30 @@ export async function POST() {
); );
} }
// Get offset from request body (for batch processing)
const body = await request.json().catch(() => ({}));
const offset = body.offset || 0;
// Fetch all songs without genres // Fetch all songs without genres
const totalUncategorized = await prisma.song.count({
where: {
genres: {
none: {}
}
}
});
if (totalUncategorized === 0) {
return Response.json({
message: 'No uncategorized songs found',
results: [],
hasMore: false,
totalUncategorized: 0,
processed: 0
});
}
// Fetch batch of songs
const uncategorizedSongs = await prisma.song.findMany({ const uncategorizedSongs = await prisma.song.findMany({
where: { where: {
genres: { genres: {
@@ -32,16 +56,11 @@ export async function POST() {
}, },
include: { include: {
genres: true genres: true
} },
take: BATCH_SIZE,
skip: offset
}); });
if (uncategorizedSongs.length === 0) {
return Response.json({
message: 'No uncategorized songs found',
results: []
});
}
// Fetch all available genres // Fetch all available genres
const allGenres = await prisma.genre.findMany({ const allGenres = await prisma.genre.findMany({
orderBy: { name: 'asc' } orderBy: { name: 'asc' }
@@ -56,7 +75,7 @@ export async function POST() {
const results: CategorizeResult[] = []; const results: CategorizeResult[] = [];
// Process each song // Process each song in this batch
for (const song of uncategorizedSongs) { for (const song of uncategorizedSongs) {
try { try {
const genreNames = allGenres.map(g => g.name); const genreNames = allGenres.map(g => g.name);
@@ -147,10 +166,15 @@ Your response:`;
} }
} }
const newOffset = offset + BATCH_SIZE;
const hasMore = newOffset < totalUncategorized;
return Response.json({ return Response.json({
message: `Processed ${uncategorizedSongs.length} songs, categorized ${results.length}`, message: `Processed ${uncategorizedSongs.length} songs in this batch, categorized ${results.length}`,
totalProcessed: uncategorizedSongs.length, totalUncategorized,
totalCategorized: results.length, processed: Math.min(newOffset, totalUncategorized),
hasMore,
nextOffset: hasMore ? newOffset : null,
results results
}); });