返回 presentation-ai
image-generation-model.tsx
根目录 / src / components / plate / ui / image-generation-model.tsx
1 import { ImagePlugin } from "@platejs/media/react";
2 import { useEditorRef } from "platejs/react";
3 import { useState } from "react";
4 import { toast } from "sonner";
5
6 import { generateImageAction } from "@/app/_actions/apps/image-studio/generate";
7 import {
8 AlertDialog,
9 AlertDialogAction,
10 AlertDialogCancel,
11 AlertDialogContent,
12 AlertDialogDescription,
13 AlertDialogFooter,
14 AlertDialogHeader,
15 AlertDialogTitle,
16 } from "@/components/ui/alert-dialog";
17 import { Input } from "@/components/ui/input";
18 import { Label } from "@/components/ui/label";
19 import {
20 Select,
21 SelectContent,
22 SelectItem,
23 SelectTrigger,
24 SelectValue,
25 } from "@/components/ui/select";
26 import { type ImageModelList } from "@/constants/image-models";
27 import { raiseError } from "@/lib/raise-error";
28 import { useNotesState } from "@/states/notes-state";
29
30 const MODEL_OPTIONS = [
31 {
32 label: "Flux 2 Flash",
33 value: "fal-ai/flux-2/flash",
34 },
35 {
36 label: "Flux Dev",
37 value: "fal-ai/flux/dev",
38 },
39 {
40 label: "Flux 2 Pro",
41 value: "fal-ai/flux-2-pro",
42 },
43 ];
44
45 function GenerateImageDialogContent({
46 setOpen,
47 isGenerating,
48 setIsGenerating,
49 }: {
50 setOpen: (value: boolean) => void;
51 isGenerating: boolean;
52 setIsGenerating: (value: boolean) => void;
53 }) {
54 const editor = useEditorRef();
55 const [prompt, setPrompt] = useState("");
56 const [selectedModel, setSelectedModel] = useState<ImageModelList>(
57 "fal-ai/flux-2/flash",
58 );
59
60 const generateImage = async () => {
61 if (!prompt.trim()) {
62 toast.error("Please enter a prompt");
63 return;
64 }
65
66 setIsGenerating(true);
67
68 try {
69 const result = await generateImageAction(prompt, selectedModel);
70
71 if (!result.success || !("image" in result) || !result.image?.url) {
72 raiseError(new Error(result.error ?? "Failed to generate image"));
73 }
74
75 editor.tf.insertNodes({
76 children: [{ text: "" }],
77 type: ImagePlugin.key,
78 url: result.image.url,
79 query: prompt,
80 });
81
82 setOpen(false);
83 toast.success("Image generated successfully!");
84 } catch (error) {
85 toast.error(
86 error instanceof Error ? error.message : "Failed to generate image",
87 );
88 setIsGenerating(false);
89 }
90 setIsGenerating(false);
91 };
92
93 return (
94 <>
95 <AlertDialogHeader>
96 <AlertDialogTitle>Generate Image with AI</AlertDialogTitle>
97 <AlertDialogDescription>
98 Enter a detailed description of the image you want to generate
99 </AlertDialogDescription>
100 </AlertDialogHeader>
101
102 <div className="space-y-4">
103 <div className="relative w-full">
104 <Label htmlFor="prompt">Prompt</Label>
105 <Input
106 id="prompt"
107 className="w-full"
108 value={prompt}
109 onChange={(e) => setPrompt(e.target.value)}
110 onKeyDown={(e) => {
111 if (e.key === "Enter" && !isGenerating) void generateImage();
112 }}
113 type="text"
114 autoFocus
115 disabled={isGenerating}
116 />
117 </div>
118
119 {isGenerating && (
120 <div className="mt-4 space-y-3">
121 <div className="h-64 w-full animate-pulse rounded-lg bg-gray-200 dark:bg-gray-800" />
122 <div className="text-center text-sm text-gray-500">
123 Generating your image…
124 </div>
125 </div>
126 )}
127 </div>
128
129 <AlertDialogFooter>
130 <Select
131 value={selectedModel}
132 onValueChange={(value) => setSelectedModel(value as ImageModelList)}
133 disabled={isGenerating}
134 >
135 <SelectTrigger className="w-full">
136 <SelectValue placeholder="Select a model" />
137 </SelectTrigger>
138 <SelectContent>
139 {MODEL_OPTIONS.map((option) => (
140 <SelectItem key={option.value} value={option.value}>
141 {option.label}
142 </SelectItem>
143 ))}
144 </SelectContent>
145 </Select>
146 <div className="flex gap-2">
147 <AlertDialogCancel disabled={isGenerating}>Cancel</AlertDialogCancel>
148 <AlertDialogAction
149 onClick={(e) => {
150 e.preventDefault();
151 void generateImage();
152 }}
153 disabled={isGenerating}
154 >
155 {isGenerating ? "Generating..." : "Generate"}
156 </AlertDialogAction>
157 </div>
158 </AlertDialogFooter>
159 </>
160 );
161 }
162
163 export default function ImageGenerationModel() {
164 const { isImageGenerationModelOpen, setIsImageGenerationModelOpen } =
165 useNotesState();
166 const [isGenerating, setIsGenerating] = useState(false);
167 return (
168 <AlertDialog
169 open={isImageGenerationModelOpen}
170 onOpenChange={(value) => {
171 setIsImageGenerationModelOpen(value);
172 setIsGenerating(false);
173 }}
174 >
175 <AlertDialogContent className="gap-6">
176 <GenerateImageDialogContent
177 setOpen={setIsImageGenerationModelOpen}
178 isGenerating={isGenerating}
179 setIsGenerating={setIsGenerating}
180 />
181 </AlertDialogContent>
182 </AlertDialog>
183 );
184 }
185
185 lines Plain Text