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,69 @@
|
||||
import { generateObject } from "ai";
|
||||
import { z } from "zod";
|
||||
import { resolveModel, type AiConfig } from "../factory";
|
||||
|
||||
const VariationsOutputSchema = z.object({
|
||||
variations: z.array(z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
changedIngredients: z.array(z.object({
|
||||
original: z.string(),
|
||||
replacement: z.string(),
|
||||
note: z.string().optional(),
|
||||
})),
|
||||
changedSteps: z.array(z.object({
|
||||
stepNumber: z.number().int(),
|
||||
change: z.string(),
|
||||
})).optional(),
|
||||
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(),
|
||||
}).optional(),
|
||||
})),
|
||||
});
|
||||
|
||||
export type VariationSuggestion = z.infer<typeof VariationsOutputSchema>["variations"][number];
|
||||
|
||||
const LANG: Record<string, string> = { en: "English", fr: "French" };
|
||||
|
||||
export async function suggestVariations(
|
||||
recipe: {
|
||||
title: string;
|
||||
description?: string | null;
|
||||
ingredients: Array<{ rawName: string; quantity?: string | null; unit?: string | null }>;
|
||||
steps: Array<{ instruction: string }>;
|
||||
},
|
||||
count = 3,
|
||||
config?: AiConfig,
|
||||
directions?: string,
|
||||
locale?: string
|
||||
): Promise<VariationSuggestion[]> {
|
||||
const model = resolveModel(config);
|
||||
const lang = LANG[locale ?? "en"] ?? "English";
|
||||
|
||||
const recipeText = [
|
||||
`Recipe: ${recipe.title}`,
|
||||
recipe.description ? `Description: ${recipe.description}` : "",
|
||||
`Ingredients: ${recipe.ingredients.map((i) => `${i.quantity ?? ""} ${i.unit ?? ""} ${i.rawName}`.trim()).join(", ")}`,
|
||||
`Steps: ${recipe.steps.map((s, i) => `${i + 1}. ${s.instruction}`).join(" | ")}`,
|
||||
].filter(Boolean).join("\n");
|
||||
|
||||
const directionsClause = directions?.trim()
|
||||
? `\n\nUser directions: ${directions.trim()}`
|
||||
: "";
|
||||
|
||||
const { object } = await generateObject({
|
||||
model,
|
||||
schema: VariationsOutputSchema,
|
||||
system:
|
||||
`You are a creative chef. Suggest meaningful variations of recipes — dietary adaptations, flavor profiles, technique changes. Each variation should be distinct and practical. Respond in ${lang}.`,
|
||||
prompt: `Suggest ${count} variations of this recipe:\n\n${recipeText}${directionsClause}`,
|
||||
});
|
||||
|
||||
return object.variations;
|
||||
}
|
||||
Reference in New Issue
Block a user