50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
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 & { userContext?: string },
|
||
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}.${config?.userContext ? `\n\nUser preferences and context:\n${config.userContext}` : ""}`,
|
||
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;
|
||
}
|