feat: auto-classify AI recipes as dish/drink (v0.33.0)
generate, generate-from-idea, URL import, and photo import all now have the AI set recipeType directly (defaulting to "dish" whenever ambiguous, per instruction), with cookMins force-cleared for drinks as a backstop in case the model doesn't follow the prompt guidance. 1-serving-by-default for unspecified drink quantities is left to the model's own judgment of the request text, since there's no reliable way to detect "was a serving count stated" without re-parsing intent. Drink recipes get a distinct default icon (no cover photo case), and Explore gains the same dish/drink Type filter My Recipes already had. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { resolveModel, type AiConfig } from "../factory";
|
||||
import { dietaryTagsSchema, ingredientSchema, stepSchema } from "./recipe-schema";
|
||||
|
||||
const RecipeOutputSchema = 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(),
|
||||
baseServings: z.number().int().min(1).max(100),
|
||||
@@ -31,9 +32,16 @@ export async function generateRecipe(
|
||||
model,
|
||||
schema: RecipeOutputSchema,
|
||||
system:
|
||||
`You are a professional chef and culinary writer. Generate detailed, accurate, and delicious recipes. For ingredients: quantity must be a number only (e.g. 0.25, 1.5, 2), unit is a separate string (e.g. 'cup', 'tbsp', 'g', 'ml'). Never combine quantity and unit into one string. Include helpful notes for ingredients when relevant.${langInstruction}${difficultyInstruction}${userInstruction}`,
|
||||
`You are a professional chef and culinary writer. Generate detailed, accurate, and delicious recipes. For ingredients: quantity must be a number only (e.g. 0.25, 1.5, 2), unit is a separate string (e.g. 'cup', 'tbsp', 'g', 'ml'). Never combine quantity and unit into one string. Include helpful notes for ingredients when relevant. Set recipeType to "drink" only for a beverage/cocktail with no cooking involved (mixing, shaking, stirring, blending) — default to "dish" whenever it's ambiguous. For a drink, never set cookMins, and default baseServings to 1 unless the request specifically asks for more than one serving/portion.${langInstruction}${difficultyInstruction}${userInstruction}`,
|
||||
prompt: `Create a recipe for: ${prompt}`,
|
||||
});
|
||||
|
||||
// Belt-and-suspenders in case the model doesn't follow the drink-specific
|
||||
// instructions above — a drink has no "cook" step by definition, and a
|
||||
// model that ignores the 1-serving-by-default guidance would otherwise
|
||||
// silently produce a batch-sized drink recipe.
|
||||
if (object.recipeType === "drink") {
|
||||
return { ...object, cookMins: undefined };
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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(),
|
||||
@@ -34,7 +35,7 @@ export async function importFromPhoto(
|
||||
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}`,
|
||||
`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",
|
||||
@@ -46,5 +47,8 @@ export async function importFromPhoto(
|
||||
],
|
||||
});
|
||||
|
||||
if (object.recipeType === "drink") {
|
||||
return { ...object, cookMins: undefined };
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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(),
|
||||
@@ -41,9 +42,12 @@ export async function importFromUrl(url: string, config?: AiConfig): Promise<Imp
|
||||
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.",
|
||||
"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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user