49 lines
2.1 KiB
TypeScript
49 lines
2.1 KiB
TypeScript
import { generateObject } from "ai";
|
|
import { z } from "zod";
|
|
import { resolveModel, type AiConfig } from "../factory";
|
|
|
|
const PairingsOutputSchema = z.object({
|
|
pairings: z.array(z.object({
|
|
name: z.string(),
|
|
role: z.enum(["starter", "side", "salad", "bread", "drink", "dessert", "sauce"]),
|
|
description: z.string(),
|
|
whyItPairs: z.string(),
|
|
difficulty: z.enum(["easy", "medium", "hard"]),
|
|
prepTimeMins: z.number().int().optional(),
|
|
})),
|
|
});
|
|
|
|
export type PairingSuggestion = z.infer<typeof PairingsOutputSchema>["pairings"][number];
|
|
|
|
const LANG: Record<string, string> = { en: "English", fr: "French" };
|
|
|
|
export async function suggestPairings(
|
|
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<PairingSuggestion[]> {
|
|
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: PairingsOutputSchema,
|
|
system:
|
|
`You are an expert chef and menu planner. Suggest complementary dishes that form a balanced, cohesive complete meal. Consider flavor profiles, cooking techniques, dietary restrictions, and cultural harmony. Avoid duplicating ingredients heavily. Suggest a balanced mix of roles (starter, sides, dessert, drinks). Respond in ${lang}.${config?.userContext ? `\n\nUser preferences and context:\n${config.userContext}` : ""}`,
|
|
prompt: `Suggest ${count} dishes that pair well with this recipe to form a complete meal:\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.pairings;
|
|
}
|