|
|
"use client"; |
|
|
import { useState } from "react"; |
|
|
import { ImageUpload } from "@/components/ImageUpload"; |
|
|
import { ImagePromptInput } from "@/components/ImagePromptInput"; |
|
|
import { ImageResultDisplay } from "@/components/ImageResultDisplay"; |
|
|
import { ImageIcon, Wand2, ExternalLink } from "lucide-react"; |
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; |
|
|
import { HistoryItem } from "@/lib/types"; |
|
|
|
|
|
export default function Home() { |
|
|
const [image, setImage] = useState<string | null>(null); |
|
|
const [generatedImage, setGeneratedImage] = useState<string | null>(null); |
|
|
const [description, setDescription] = useState<string | null>(null); |
|
|
const [loading, setLoading] = useState(false); |
|
|
const [error, setError] = useState<string | null>(null); |
|
|
const [history, setHistory] = useState<HistoryItem[]>([]); |
|
|
|
|
|
const handleImageSelect = (imageData: string) => { |
|
|
setImage(imageData || null); |
|
|
}; |
|
|
|
|
|
const handlePromptSubmit = async (prompt: string) => { |
|
|
try { |
|
|
setLoading(true); |
|
|
setError(null); |
|
|
|
|
|
|
|
|
const imageToEdit = generatedImage || image; |
|
|
|
|
|
|
|
|
const requestData = { |
|
|
prompt, |
|
|
image: imageToEdit, |
|
|
history: history.length > 0 ? history : undefined, |
|
|
}; |
|
|
|
|
|
const response = await fetch("/api/image", { |
|
|
method: "POST", |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
}, |
|
|
body: JSON.stringify(requestData), |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
const errorData = await response.json(); |
|
|
throw new Error(errorData.error || "Failed to generate image"); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (data.image) { |
|
|
|
|
|
setGeneratedImage(data.image); |
|
|
setDescription(data.description || null); |
|
|
|
|
|
|
|
|
const userMessage: HistoryItem = { |
|
|
role: "user", |
|
|
parts: [ |
|
|
{ text: prompt }, |
|
|
...(imageToEdit ? [{ image: imageToEdit }] : []), |
|
|
], |
|
|
}; |
|
|
|
|
|
|
|
|
const aiResponse: HistoryItem = { |
|
|
role: "model", |
|
|
parts: [ |
|
|
...(data.description ? [{ text: data.description }] : []), |
|
|
...(data.image ? [{ image: data.image }] : []), |
|
|
], |
|
|
}; |
|
|
|
|
|
|
|
|
setHistory((prevHistory) => [...prevHistory, userMessage, aiResponse]); |
|
|
} else { |
|
|
setError("No image returned from API"); |
|
|
} |
|
|
} catch (error) { |
|
|
setError(error instanceof Error ? error.message : "An error occurred"); |
|
|
console.error("Error processing request:", error); |
|
|
} finally { |
|
|
setLoading(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleReset = () => { |
|
|
setImage(null); |
|
|
setGeneratedImage(null); |
|
|
setDescription(null); |
|
|
setLoading(false); |
|
|
setError(null); |
|
|
setHistory([]); |
|
|
}; |
|
|
|
|
|
|
|
|
const currentImage = generatedImage || image; |
|
|
const isEditing = !!currentImage; |
|
|
|
|
|
|
|
|
const displayImage = generatedImage; |
|
|
|
|
|
return ( |
|
|
<main className="min-h-screen flex items-center justify-center bg-background p-8"> |
|
|
<Card className="w-full max-w-4xl border-0 bg-card shadow-none"> |
|
|
<CardHeader className="flex flex-col items-center justify-center space-y-2"> |
|
|
<CardTitle className="flex items-center gap-2 text-foreground"> |
|
|
<Wand2 className="w-8 h-8 text-primary" /> |
|
|
Image Generation & Editing |
|
|
</CardTitle> |
|
|
<span className="text-sm font-mono text-muted-foreground"> |
|
|
powered by Google DeepMind Gemini 2.0 Flash |
|
|
</span> |
|
|
</CardHeader> |
|
|
<CardContent className="space-y-6 pt-6 w-full"> |
|
|
<div className="p-4 mb-4 text-sm text-blue-700 bg-blue-100 rounded-lg flex items-center justify-between"> |
|
|
<span> |
|
|
Build your own image generation apps with the Gemini API! |
|
|
</span> |
|
|
<a |
|
|
href="https://ai.google.dev/gemini-api/docs/image-generation" |
|
|
target="_blank" |
|
|
rel="noopener noreferrer" |
|
|
className="flex items-center gap-1 font-medium hover:underline" |
|
|
> |
|
|
View Docs <ExternalLink className="h-3 w-3" /> |
|
|
</a> |
|
|
</div> |
|
|
|
|
|
{error && ( |
|
|
<div className="p-4 mb-4 text-sm text-red-700 bg-red-100 rounded-lg"> |
|
|
{error} |
|
|
</div> |
|
|
)} |
|
|
|
|
|
{!displayImage && !loading ? ( |
|
|
<> |
|
|
<ImageUpload |
|
|
onImageSelect={handleImageSelect} |
|
|
currentImage={currentImage} |
|
|
/> |
|
|
<ImagePromptInput |
|
|
onSubmit={handlePromptSubmit} |
|
|
isEditing={isEditing} |
|
|
isLoading={loading} |
|
|
/> |
|
|
</> |
|
|
) : loading ? ( |
|
|
<div |
|
|
role="status" |
|
|
className="flex items-center mx-auto justify-center h-56 max-w-sm bg-gray-300 rounded-lg animate-pulse dark:bg-secondary" |
|
|
> |
|
|
<ImageIcon className="w-10 h-10 text-gray-200 dark:text-muted-foreground" /> |
|
|
<span className="pl-4 font-mono font-xs text-muted-foreground"> |
|
|
Processing... |
|
|
</span> |
|
|
</div> |
|
|
) : ( |
|
|
<> |
|
|
<ImageResultDisplay |
|
|
imageUrl={displayImage || ""} |
|
|
description={description} |
|
|
onReset={handleReset} |
|
|
conversationHistory={history} |
|
|
/> |
|
|
<ImagePromptInput |
|
|
onSubmit={handlePromptSubmit} |
|
|
isEditing={true} |
|
|
isLoading={loading} |
|
|
/> |
|
|
</> |
|
|
)} |
|
|
</CardContent> |
|
|
</Card> |
|
|
</main> |
|
|
); |
|
|
} |
|
|
|