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)"), 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.${langInstruction}`, messages: [ { role: "user", content: [ { type: "image", image: imageBase64, mediaType: mimeType }, { type: "text", text: "Extract the complete recipe from this image." }, ], }, ], }); return object; }