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,49 @@
|
||||
import { generateObject } from "ai";
|
||||
import { z } from "zod";
|
||||
import { resolveModel, type AiConfig } from "../factory";
|
||||
|
||||
const DrinksOutputSchema = z.object({
|
||||
drinks: z.array(z.object({
|
||||
name: z.string(),
|
||||
type: z.enum(["wine", "beer", "cocktail", "spirit", "non-alcoholic", "hot"]),
|
||||
alcoholic: z.boolean(),
|
||||
description: z.string(),
|
||||
examples: z.array(z.string()).min(1).max(3),
|
||||
whyItPairs: z.string(),
|
||||
servingTip: z.string().optional(),
|
||||
})),
|
||||
});
|
||||
|
||||
export type DrinkSuggestion = z.infer<typeof DrinksOutputSchema>["drinks"][number];
|
||||
|
||||
const LANG: Record<string, string> = { en: "English", fr: "French" };
|
||||
|
||||
export async function suggestDrinks(
|
||||
recipe: {
|
||||
title: string;
|
||||
description?: string | null;
|
||||
difficulty?: string | null;
|
||||
dietaryTags?: Record<string, boolean> | null;
|
||||
ingredients: Array<{ rawName: string }>;
|
||||
},
|
||||
count = 4,
|
||||
config?: AiConfig,
|
||||
locale?: string
|
||||
): Promise<DrinkSuggestion[]> {
|
||||
const model = resolveModel(config);
|
||||
const lang = LANG[locale ?? "en"] ?? "English";
|
||||
|
||||
const dietaryContext = recipe.dietaryTags
|
||||
? Object.entries(recipe.dietaryTags).filter(([, v]) => v).map(([k]) => k).join(", ")
|
||||
: "";
|
||||
|
||||
const { object } = await generateObject({
|
||||
model,
|
||||
schema: DrinksOutputSchema,
|
||||
system:
|
||||
`You are a sommelier and beverage expert. For each suggestion, lead with a style or category (e.g. 'Dry white Burgundy', 'Session IPA', 'Sparkling water with citrus') rather than a specific product. Then provide 2–3 concrete examples to illustrate the style. Consider flavor profiles, acidity, tannins, sweetness, weight, and cultural pairing traditions. Include a mix of alcoholic and non-alcoholic options. Respond in ${lang}.`,
|
||||
prompt: `Suggest ${count} drinks to serve with this recipe.\n\nRecipe: ${recipe.title}\n${recipe.description ? `Description: ${recipe.description}` : ""}\n${dietaryContext ? `Dietary: ${dietaryContext}` : ""}\nKey ingredients: ${recipe.ingredients.slice(0, 8).map((i) => i.rawName).join(", ")}`,
|
||||
});
|
||||
|
||||
return object.drinks;
|
||||
}
|
||||
Reference in New Issue
Block a user