feat(ai): Vercel AI SDK provider factory with BYOK and per-use-case model prefs

Provider factory (OpenAI/Anthropic/OpenRouter/Ollama). generateObject for all outputs.
Features: recipe generation, photo-to-recipe vision, variations, drink/meal pairings,
ingredient substitution, recipe adaptation, nutrition analysis, URL import, meal plan gen.
User BYOK keys (AES-256-GCM). Per-use-case model preferences (text/vision/mealPlan).
Site-level key override via admin settings.
This commit is contained in:
Arnaud
2026-07-01 08:10:19 +02:00
parent 84d6cfeb07
commit d9d58fd01a
26 changed files with 1656 additions and 0 deletions
@@ -0,0 +1,52 @@
import { generateObject } from "ai";
import { z } from "zod";
import { resolveModel, type AiConfig } from "../factory";
const RecipeOutputSchema = z.object({
title: z.string(),
description: z.string(),
baseServings: z.number().int().min(1).max(100),
prepMins: z.number().int().min(0).optional(),
cookMins: z.number().int().min(0).optional(),
difficulty: z.enum(["easy", "medium", "hard"]),
dietaryTags: z.object({
vegan: z.boolean().optional(),
vegetarian: z.boolean().optional(),
glutenFree: z.boolean().optional(),
dairyFree: z.boolean().optional(),
nutFree: z.boolean().optional(),
halal: z.boolean().optional(),
kosher: z.boolean().optional(),
}),
ingredients: z.array(z.object({
rawName: z.string(),
quantity: z.number().optional(),
unit: z.string().optional(),
note: z.string().optional(),
})),
steps: z.array(z.object({
instruction: z.string(),
timerSeconds: z.number().int().optional(),
})),
});
export type GeneratedRecipe = z.infer<typeof RecipeOutputSchema>;
export async function generateRecipe(
prompt: string,
config?: AiConfig & { language?: string }
): Promise<GeneratedRecipe> {
const model = resolveModel(config);
const lang = config?.language ?? "en";
const langInstruction = lang !== "en" ? ` Write the entire recipe (title, description, ingredient names, step instructions) in ${lang}.` : "";
const { object } = await generateObject({
model,
schema: RecipeOutputSchema,
system:
`You are a professional chef and culinary writer. Generate detailed, accurate, and delicious recipes. For ingredients: quantity must be a number only (e.g. 0.25, 1.5, 2), unit is a separate string (e.g. 'cup', 'tbsp', 'g', 'ml'). Never combine quantity and unit into one string. Include helpful notes for ingredients when relevant.${langInstruction}`,
prompt: `Create a recipe for: ${prompt}`,
});
return object;
}