import { generateObject } from "ai"; import { z } from "zod"; import { resolveModel, type AiConfig } from "../factory"; import { dietaryTagsSchema, ingredientSchema, stepSchema } from "./recipe-schema"; const ImportedRecipeSchema = z.object({ found: z.boolean().describe("false if the image does not contain a recognizable recipe (e.g. random photo, no visible ingredients/instructions)"), recipeType: z.enum(["dish", "drink"]).describe("\"drink\" only for a beverage/cocktail (no cooking step) — \"dish\" for anything else, including if unsure."), title: z.string(), description: z.string().optional(), baseServings: z.number().int().min(1).optional(), prepMins: z.number().int().min(0).optional(), cookMins: z.number().int().min(0).optional(), difficulty: z.enum(["easy", "medium", "hard"]).optional(), dietaryTags: dietaryTagsSchema.optional(), ingredients: z.array(ingredientSchema(z.string())), steps: z.array(stepSchema), }); export type ImportedRecipe = z.infer; const LANG: Record = { en: "English", fr: "French" }; export async function importFromPhoto( imageBase64: string, mimeType: "image/jpeg" | "image/png" | "image/webp", config?: AiConfig, locale?: string, ): Promise { const model = resolveModel(config); const lang = LANG[locale ?? "en"] ?? "English"; const langInstruction = lang !== "English" ? ` Write the entire recipe (title, description, ingredient names, step instructions) in ${lang}, regardless of the language used in the image.` : ""; const { object } = await generateObject({ model, schema: ImportedRecipeSchema, system: `You are a recipe extraction specialist. Extract the complete recipe from the provided image. Be precise with ingredient quantities and cooking instructions. If the image does not contain a recognizable recipe (no visible ingredients or cooking instructions), set found to false and leave the other fields minimal/empty. Set recipeType to "drink" only for a beverage/cocktail with no cooking involved — never set cookMins for a drink.${langInstruction}`, messages: [ { role: "user", content: [ { type: "image", image: imageBase64, mediaType: mimeType }, { type: "text", text: "Extract the complete recipe from this image." }, ], }, ], }); if (object.recipeType === "drink") { return { ...object, cookMins: undefined }; } return object; }