feat: cooking assistant can propose creating a recipe
Gives the general chat a createRecipe tool (Vercel AI SDK, stepCountIs(3)) scoped so it can only ever produce a draft — the tool's execute is a pure echo, no DB write. The route surfaces the tool call as proposedRecipe alongside the normal text answer; the chat UI renders it as a card with explicit Create/Discard buttons. Create POSTs to the existing /api/v1/recipes endpoint — the same code path and tier/recipe-count limit the manual editor already goes through — so there's exactly one place that actually creates a recipe row, and nothing happens without the user clicking Create. Scoped to the general assistant only (not per-recipe chat), and to one tool for now — addToShoppingList/generateMealPlan are follow-ups. v0.45.0
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
const CreateRecipeInputSchema = z.object({
|
||||
title: z.string().min(1).max(200),
|
||||
description: z.string().max(2000).optional(),
|
||||
baseServings: z.number().int().min(1).max(100).default(4),
|
||||
recipeType: z.enum(["dish", "drink"]).default("dish"),
|
||||
difficulty: z.enum(["easy", "medium", "hard"]).optional(),
|
||||
prepMins: z.number().int().min(0).max(1440).optional(),
|
||||
cookMins: z.number().int().min(0).max(1440).optional(),
|
||||
ingredients: z.array(z.object({
|
||||
rawName: z.string().min(1).max(200).describe("Ingredient name only, e.g. 'flour' — never combined with quantity/unit"),
|
||||
quantity: z.number().optional().describe("A number only, e.g. 0.25, 1.5, 2"),
|
||||
unit: z.string().max(50).optional().describe("e.g. 'cup', 'tbsp', 'g'"),
|
||||
})).min(1).max(100),
|
||||
steps: z.array(z.object({
|
||||
instruction: z.string().min(1).max(2000),
|
||||
})).min(1).max(100),
|
||||
});
|
||||
|
||||
export type CreateRecipeToolInput = z.infer<typeof CreateRecipeInputSchema>;
|
||||
|
||||
/**
|
||||
* Lets the cooking assistant propose a new recipe from the conversation.
|
||||
* Deliberately does NOT write to the database — `execute` just validates and
|
||||
* echoes the input back as a draft. The chat UI renders that draft with an
|
||||
* explicit Confirm button, which POSTs to the existing /api/v1/recipes
|
||||
* endpoint (the same one the manual recipe editor uses) — so nothing gets
|
||||
* created without the user directly approving it, and there's exactly one
|
||||
* code path that actually writes a recipe row.
|
||||
*/
|
||||
export const createRecipeTool = tool({
|
||||
description:
|
||||
"Propose creating a new recipe based on the conversation. This only drafts the recipe for the user to review — it does not save anything. Only call this when the user has actually asked you to create/save/write down a recipe, not just discussed one.",
|
||||
inputSchema: CreateRecipeInputSchema,
|
||||
execute: async (input: CreateRecipeToolInput) => input,
|
||||
});
|
||||
Reference in New Issue
Block a user