import { generateObject } from "ai"; import { z } from "zod"; import { resolveModel, type AiConfig } from "../factory"; import { safeFetch } from "@/lib/validate-webhook-url"; import { dietaryTagsSchema, ingredientSchema, stepSchema } from "./recipe-schema"; const ImportedRecipeSchema = z.object({ 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; export async function importFromUrl(url: string, config?: AiConfig): Promise { const res = await safeFetch(url, { headers: { "User-Agent": "Mozilla/5.0 (compatible; Epicure/1.0; recipe importer)" }, signal: AbortSignal.timeout(10000), }); if (!res.ok) throw new Error(`Failed to fetch URL: ${res.status}`); const html = await res.text(); // strip scripts/styles, take first 30k chars to stay within context const cleaned = html .replace(//gi, "") .replace(//gi, "") .replace(/<[^>]+>/g, " ") .replace(/\s+/g, " ") .slice(0, 30000); const model = resolveModel(config); const { object } = await generateObject({ model, schema: ImportedRecipeSchema, system: "You are an expert at extracting structured recipe data from web page text. Extract all recipe information accurately. For ingredients, separate quantity, unit, and name. For steps, extract timer durations in seconds when mentioned. Set recipeType to \"drink\" only for a beverage/cocktail with no cooking involved — never set cookMins for a drink.", prompt: `Extract the recipe from this web page:\n\nURL: ${url}\n\nContent:\n${cleaned}`, }); if (object.recipeType === "drink") { return { ...object, cookMins: undefined }; } return object; }