Fix: Add batch processing to prevent timeout on large song libraries
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user