import { generateObject } from "ai"; import { z } from "zod"; import { resolveModel, type AiConfig } from "../factory"; import { dietaryTagsSchema, ingredientSchema, stepSchema } from "./recipe-schema"; import type { PhotoRecognition } from "./recognize-photo"; const RecipeOutputSchema = z.object({ title: z.string(), description: z.string().optional(), baseServings: z.number().int().min(1).max(100), prepMins: z.number().int().min(0).optional(), cookMins: z.number().int().min(0).optional(), difficulty: z.enum(["easy", "medium", "hard"]), dietaryTags: dietaryTagsSchema, ingredients: z.array(ingredientSchema(z.number())), steps: z.array(stepSchema), }); export type GeneratedRecipeFromRecognition = z.infer; /** Step 2 of the photo-import flow: a text model reconstructs the full recipe * (quantities, steps, timing) from what the vision model recognized in * `recognizePhoto` — it never sees the photo itself. */ export async function generateRecipeFromRecognition( recognition: PhotoRecognition, config?: AiConfig & { language?: string }, ): Promise { const model = resolveModel(config); const lang = config?.language ?? "en"; const langInstruction = lang !== "en" ? ` Write the entire recipe (title, description, ingredient names, step instructions) in ${lang}.` : ""; const drinkInstruction = recognition.recipeType === "drink" ? " This is a drink: never set cookMins, and default baseServings to 1 unless the description implies more than one serving." : ""; const { object } = await generateObject({ model, schema: RecipeOutputSchema, system: `You are a professional chef and culinary writer. A vision model has already identified what's in a photo — reconstruct the complete, precise recipe implied by that description. For ingredients: quantity must be a number only (e.g. 0.25, 1.5, 2), unit is a separate string. Never combine quantity and unit into one string.${drinkInstruction}${langInstruction}`, prompt: `Photo shows: "${recognition.title}"\nVisible ingredients: ${recognition.visibleIngredients.join(", ") || "none clearly visible"}\nDescription: ${recognition.description}\n\nWrite the complete recipe for this ${recognition.recipeType}.`, }); if (recognition.recipeType === "drink") { return { ...object, cookMins: undefined }; } return object; }