fix: variations/adapt-recipe silent errors and no-op duplicates
AI adapt/variations flows had no catch on their fetch calls, so a network failure surfaced as nothing happening rather than an error toast. They also unconditionally persisted the AI's output even when it was identical to the source recipe, and the adapt route never recorded a recipeVariations row (only plain forks did) or enforced the per-tier recipe-count limit other create paths already check. v0.39.0
This commit is contained in:
@@ -75,12 +75,18 @@ export function AdaptRecipeButton({
|
||||
return;
|
||||
}
|
||||
|
||||
const { id, adaptationNotes: notes } = await res.json() as { id: string; adaptationNotes: string };
|
||||
const { id, adaptationNotes: notes, unchanged } = await res.json() as { id: string; adaptationNotes: string; unchanged?: boolean };
|
||||
setAdaptationNotes(notes);
|
||||
toast.success(t("adapted"));
|
||||
setOpen(false);
|
||||
reset();
|
||||
if (unchanged) {
|
||||
toast.info(t("adaptUnchanged"));
|
||||
return;
|
||||
}
|
||||
toast.success(t("adapted"));
|
||||
router.push(`/recipes/${id}/edit`);
|
||||
} catch {
|
||||
toast.error(t("adaptFailed"));
|
||||
} finally {
|
||||
setAdapting(false);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { isRecipeUnchanged } from "@/lib/recipe-diff";
|
||||
|
||||
type Variation = {
|
||||
title: string;
|
||||
@@ -86,6 +87,8 @@ export function VariationsDialog({
|
||||
}
|
||||
const data = await res.json() as { variations: Variation[] };
|
||||
setVariations(data.variations);
|
||||
} catch {
|
||||
toast.error(tv("error"));
|
||||
} finally {
|
||||
setGenerating(false);
|
||||
}
|
||||
@@ -112,6 +115,16 @@ export function VariationsDialog({
|
||||
return step;
|
||||
});
|
||||
|
||||
// The AI's changedIngredients/changedSteps are matched against the
|
||||
// original by substring — if none of them actually matched anything
|
||||
// (free-text vs. free-text mismatch), this "variation" is really just
|
||||
// a byte-for-byte copy. Saving it would silently create a duplicate
|
||||
// recipe with nothing different from the original.
|
||||
if (isRecipeUnchanged({ baseServings, ingredients, steps }, { baseServings, ingredients: updatedIngredients, steps: updatedSteps })) {
|
||||
toast.error(tv("noChanges"));
|
||||
return;
|
||||
}
|
||||
|
||||
const saveRes = await fetch("/api/v1/recipes", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -149,6 +162,8 @@ export function VariationsDialog({
|
||||
toast.success(tv("success"));
|
||||
onOpenChange(false);
|
||||
router.push(`/recipes/${saved.id}/edit`);
|
||||
} catch {
|
||||
toast.error(tv("saveError"));
|
||||
} finally {
|
||||
setApplying(null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user