Files
homepage/src/routes/api/generate-alt-text-bulk/+server.ts
T
Alexander d2ac67fb44 fix: resolve all 1008 svelte-check type errors across codebase
Add type annotations, JSDoc types, null checks, and proper generics
to eliminate all svelte-check errors. Key changes include:
- Type $state(null) variables to avoid 'never' inference
- Add JSDoc typedefs for plain <script> components
- Fix mongoose model typing with Model<any> to avoid union complexity
- Add App.Error/App.PageState interfaces in app.d.ts
- Fix tuple types to array types in types.ts
- Type catch block errors and API handler params
- Add null safety for DOM queries and optional chaining
- Add standard line-clamp property alongside -webkit- prefix
2026-03-02 08:40:18 +01:00

184 lines
4.5 KiB
TypeScript

import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { generateAltText, type RecipeContext } from '$lib/server/ai/alttext.js';
import { checkOllamaHealth } from '$lib/server/ai/ollama.js';
import { Recipe } from '$models/Recipe.js';
export const POST: RequestHandler = async ({ request, locals }) => {
// Check authentication
const session = await locals.auth();
if (!session?.user) {
throw error(401, 'Unauthorized');
}
try {
const body = await request.json();
const { filter = 'missing', limit = 10, modelName } = body;
// Check if Ollama is available
const isOllamaAvailable = await checkOllamaHealth();
if (!isOllamaAvailable) {
throw error(503, 'Ollama service is not available. Make sure Ollama is running.');
}
// Build query based on filter
let query: any = { images: { $exists: true, $ne: [] } };
if (filter === 'missing') {
// Find recipes with images but missing alt text
query = {
images: {
$elemMatch: {
mediapath: { $exists: true },
$or: [{ alt: { $exists: false } }, { alt: '' }],
},
},
};
} else if (filter === 'all') {
// Process all recipes with images
query = { images: { $exists: true, $ne: [] } };
}
// Fetch recipes
const recipes = await Recipe.find(query).limit(limit);
if (recipes.length === 0) {
return json({
success: true,
processed: 0,
message: 'No recipes found matching criteria',
});
}
const results: Array<{
shortName: string;
name: string;
processed: number;
failed: number;
}> = [];
// Process each recipe
for (const recipe of recipes) {
let processed = 0;
let failed = 0;
for (let i = 0; i < recipe.images.length; i++) {
const image = recipe.images[i];
// Skip if alt text exists and we're only processing missing ones
if (filter === 'missing' && image.alt) {
continue;
}
try {
// Prepare context
const context: RecipeContext = {
name: recipe.name,
category: recipe.category,
tags: recipe.tags,
};
// Generate alt text
const altTextResult = await generateAltText(
image.mediapath,
context,
modelName || 'gemma3:latest'
);
// Update German alt text
recipe.images[i].alt = altTextResult.de;
// Ensure translations.en.images array exists
if (!recipe.translations) {
(recipe as any).translations = { en: { images: [] } };
}
if (!recipe.translations!.en) {
(recipe.translations as any).en = { images: [] };
}
if (!recipe.translations!.en!.images) {
(recipe.translations!.en as any).images = [];
}
// Ensure array has enough entries
while (recipe.translations!.en!.images!.length <= i) {
recipe.translations!.en!.images!.push({ alt: '', caption: '' } as any);
}
// Update English alt text
recipe.translations!.en!.images![i].alt = altTextResult.en;
processed++;
} catch (err) {
console.error(`Failed to process image ${i} for recipe ${recipe.short_name}:`, err);
failed++;
}
}
// Save recipe if any images were processed
if (processed > 0) {
await recipe.save();
}
results.push({
shortName: recipe.short_name,
name: recipe.name,
processed,
failed,
});
}
return json({
success: true,
processed: results.reduce((sum, r) => sum + r.processed, 0),
failed: results.reduce((sum, r) => sum + r.failed, 0),
results,
});
} catch (err) {
console.error('Error in bulk alt text generation:', err);
if (err instanceof Error && 'status' in err) {
throw err;
}
throw error(500, err instanceof Error ? err.message : 'Failed to generate alt text');
}
};
/**
* GET endpoint to check status and get stats
*/
export const GET: RequestHandler = async ({ locals }) => {
// Check authentication
const session = await locals.auth();
if (!session?.user) {
throw error(401, 'Unauthorized');
}
try {
// Count recipes with missing alt text
const totalWithImages = await Recipe.countDocuments({
images: { $exists: true, $ne: [] },
});
const missingAltText = await Recipe.countDocuments({
images: {
$elemMatch: {
mediapath: { $exists: true },
$or: [{ alt: { $exists: false } }, { alt: '' }],
},
},
});
// Check Ollama health
const ollamaAvailable = await checkOllamaHealth();
return json({
totalWithImages,
missingAltText,
ollamaAvailable,
});
} catch (err) {
throw error(500, 'Failed to fetch statistics');
}
};