"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { FakeProgressBar } from "@/components/ui/fake-progress-bar"; import { Sparkles, Loader2, ChevronRight, Replace } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; type Variation = { title: string; description: string; changedIngredients: Array<{ original: string; replacement: string; note?: string }>; changedSteps?: Array<{ stepNumber: number; change: string }>; dietaryTags?: Record; }; type Ingredient = { rawName: string; quantity?: string | number | null; unit?: string | null; note?: string | null; order: number; }; type Step = { instruction: string; timerSeconds?: number | null; order: number; }; export function VariationsDialog({ recipeId, baseServings, difficulty, prepMins, cookMins, ingredients, steps, open, onOpenChange, }: { recipeId: string; baseServings: number; difficulty?: string | null; prepMins?: number | null; cookMins?: number | null; ingredients: Ingredient[]; steps: Step[]; open: boolean; onOpenChange: (open: boolean) => void; }) { const t = useTranslations("recipe"); const router = useRouter(); const [generating, setGenerating] = useState(false); const [applying, setApplying] = useState(null); const [variations, setVariations] = useState([]); const [directions, setDirections] = useState(""); async function generate() { setGenerating(true); setVariations([]); try { const res = await fetch(`/api/v1/ai/variations/${recipeId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ count: 3, directions: directions.trim() || undefined }), }); if (!res.ok) { const err = await res.json() as { error?: string }; toast.error(err.error ?? "Failed to generate variations"); return; } const data = await res.json() as { variations: Variation[] }; setVariations(data.variations); } finally { setGenerating(false); } } async function apply(variation: Variation, index: number) { setApplying(index); try { const updatedIngredients = ingredients.map((ing) => { const change = variation.changedIngredients.find( (c) => ing.rawName.toLowerCase().includes(c.original.toLowerCase()) ); if (change) { return { ...ing, rawName: change.replacement, note: change.note ?? ing.note }; } return ing; }); const updatedSteps = steps.map((step, i) => { const change = variation.changedSteps?.find((c) => c.stepNumber === i + 1); if (change) { return { ...step, instruction: `${step.instruction} [Variation: ${change.change}]` }; } return step; }); const saveRes = await fetch("/api/v1/recipes", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: variation.title, description: variation.description, baseServings, difficulty, prepMins, cookMins, dietaryTags: variation.dietaryTags ?? {}, visibility: "private", aiGenerated: true, ingredients: updatedIngredients.map((ing, i) => ({ rawName: ing.rawName, quantity: ing.quantity ? String(ing.quantity) : undefined, unit: ing.unit ?? undefined, note: ing.note ?? undefined, order: i, })), steps: updatedSteps.map((step, i) => ({ instruction: step.instruction, timerSeconds: step.timerSeconds ?? undefined, order: i, })), }), }); if (!saveRes.ok) { toast.error("Failed to save variation"); return; } const saved = await saveRes.json() as { id: string }; toast.success("Variation created — review and edit before publishing"); onOpenChange(false); router.push(`/recipes/${saved.id}/edit`); } finally { setApplying(null); } } return ( AI Recipe Variations Generate creative variations of this recipe — dietary adaptations, flavor twists, technique changes. {variations.length === 0 ? (