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:
@@ -0,0 +1,52 @@
|
||||
import { generateObject } from "ai";
|
||||
import { z } from "zod";
|
||||
import { resolveModel, type AiConfig } from "../factory";
|
||||
|
||||
const ScaledIngredientsSchema = z.object({
|
||||
ingredients: z.array(
|
||||
z.object({
|
||||
rawName: z.string(),
|
||||
quantity: z.string(),
|
||||
unit: z.string().nullable(),
|
||||
note: z.string().optional(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type ScaledIngredient = z.infer<typeof ScaledIngredientsSchema>["ingredients"][number];
|
||||
|
||||
type RecipeInput = {
|
||||
title: string;
|
||||
baseServings: number;
|
||||
ingredients: Array<{
|
||||
rawName: string;
|
||||
quantity: string | null;
|
||||
unit: string | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
export async function scaleRecipe(
|
||||
recipe: RecipeInput,
|
||||
targetServings: number,
|
||||
originalServings: number,
|
||||
config?: AiConfig,
|
||||
locale?: string
|
||||
): Promise<ScaledIngredient[]> {
|
||||
const model = resolveModel(config);
|
||||
|
||||
const ingredientList = recipe.ingredients
|
||||
.map((ing) => {
|
||||
const parts = [ing.quantity, ing.unit, ing.rawName].filter(Boolean);
|
||||
return `- ${parts.join(" ")}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const { object } = await generateObject({
|
||||
model,
|
||||
schema: ScaledIngredientsSchema,
|
||||
system: `You are a culinary scaling expert. Scale recipe ingredients from ${originalServings} to ${targetServings} servings. Use practical quantities — no 0.33 eggs (round to whole), no 0.17 cans (use 0.25 or 0.5), avoid impractical fractions. Substitute whole units where sensible. Add a note when rounding significantly.`,
|
||||
prompt: `Recipe: "${recipe.title}"\n\nIngredients to scale:\n${ingredientList}`,
|
||||
});
|
||||
|
||||
return object.ingredients;
|
||||
}
|
||||
Reference in New Issue
Block a user