Update features and dependencies
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Sparkles, Loader2, Camera, Type, Upload, X } from "lucide-react";
|
||||
import { Sparkles, Loader2, Camera, Type, Upload, X, Shuffle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -30,6 +30,24 @@ const LANGUAGES = [
|
||||
{ code: "ar", label: "Arabic" },
|
||||
];
|
||||
|
||||
const SURPRISE_PROMPTS = [
|
||||
"A cozy one-pot dish with whatever is in my pantry on a rainy evening",
|
||||
"A vibrant street food recipe from Southeast Asia",
|
||||
"A classic French bistro dish reimagined as a weeknight dinner",
|
||||
"A hearty plant-based bowl with bold spices",
|
||||
"A 5-ingredient pasta with pantry staples",
|
||||
"An unexpected fusion of Japanese and Mexican flavors",
|
||||
"A light summer salad with fresh herbs and a citrus dressing",
|
||||
"A slow-cooked braise that fills the house with aroma",
|
||||
"A festive appetizer that looks impressive but is easy to make",
|
||||
"A warming spiced soup from the Middle East",
|
||||
"A crowd-pleasing comfort food with a twist",
|
||||
"A 20-minute weeknight dinner using chicken thighs",
|
||||
"A rich chocolate dessert with a molten center",
|
||||
"A crispy baked dish that tastes deep-fried",
|
||||
"A refreshing no-cook meal for hot summer days",
|
||||
];
|
||||
|
||||
type Tab = "describe" | "photo";
|
||||
|
||||
type GeneratedRecipe = {
|
||||
@@ -57,6 +75,7 @@ export function AiGenerateDialog({
|
||||
// describe tab
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [language, setLanguage] = useState("en");
|
||||
const [difficulty, setDifficulty] = useState<"" | "easy" | "medium" | "hard">("");
|
||||
|
||||
// photo tab
|
||||
const [photoPreview, setPhotoPreview] = useState<string | null>(null);
|
||||
@@ -99,7 +118,7 @@ export function AiGenerateDialog({
|
||||
const res = await fetch("/api/v1/ai/generate", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ prompt: prompt.trim(), language }),
|
||||
body: JSON.stringify({ prompt: prompt.trim(), language, difficulty: difficulty || undefined }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json() as { error?: string };
|
||||
@@ -110,7 +129,7 @@ export function AiGenerateDialog({
|
||||
const saveRes = await fetch("/api/v1/recipes", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ...generated, visibility: "private", aiGenerated: true }),
|
||||
body: JSON.stringify({ ...generated, visibility: "private", aiGenerated: true, language }),
|
||||
});
|
||||
if (!saveRes.ok) { toast.error("Failed to save generated recipe"); return; }
|
||||
const saved = await saveRes.json() as { id: string };
|
||||
@@ -188,7 +207,21 @@ export function AiGenerateDialog({
|
||||
{tab === "describe" && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-prompt">What do you want to cook?</Label>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="ai-prompt">What do you want to cook?</Label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const idx = Math.floor(Math.random() * SURPRISE_PROMPTS.length);
|
||||
setPrompt(SURPRISE_PROMPTS[idx]!);
|
||||
}}
|
||||
disabled={busy}
|
||||
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-primary transition-colors disabled:opacity-50"
|
||||
>
|
||||
<Shuffle className="h-3 w-3" />
|
||||
Surprise me
|
||||
</button>
|
||||
</div>
|
||||
<Textarea
|
||||
id="ai-prompt"
|
||||
value={prompt}
|
||||
@@ -198,18 +231,34 @@ export function AiGenerateDialog({
|
||||
disabled={busy}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Recipe language</Label>
|
||||
<Select value={language} onValueChange={(v) => v && setLanguage(v)} disabled={busy}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANGUAGES.map((l) => (
|
||||
<SelectItem key={l.code} value={l.code}>{l.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Recipe language</Label>
|
||||
<Select value={language} onValueChange={(v) => v && setLanguage(v)} disabled={busy}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANGUAGES.map((l) => (
|
||||
<SelectItem key={l.code} value={l.code}>{l.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Difficulty</Label>
|
||||
<Select value={difficulty} onValueChange={(v) => setDifficulty(v as typeof difficulty)} disabled={busy}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Any" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">Any</SelectItem>
|
||||
<SelectItem value="easy">Easy</SelectItem>
|
||||
<SelectItem value="medium">Medium</SelectItem>
|
||||
<SelectItem value="hard">Hard</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -267,7 +316,7 @@ export function AiGenerateDialog({
|
||||
/>
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2 justify-end pt-1">
|
||||
<Button variant="outline" onClick={handleClose} disabled={busy}>
|
||||
<Button variant="ghost" onClick={handleClose} disabled={busy}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user