| import React, { useState, useCallback } from 'react'; | |
| import { editImage } from '../services/geminiService'; | |
| import { fileToBase64 } from '../utils'; | |
| import { Spinner } from '../components/Spinner'; | |
| const ImageEditModule: React.FC = () => { | |
| const [file, setFile] = useState<File | null>(null); | |
| const [prompt, setPrompt] = useState<string>(''); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| const [originalImage, setOriginalImage] = useState<string | null>(null); | |
| const [editedImage, setEditedImage] = useState<string | null>(null); | |
| const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| if (e.target.files && e.target.files[0]) { | |
| const selectedFile = e.target.files[0]; | |
| setFile(selectedFile); | |
| setOriginalImage(URL.createObjectURL(selectedFile)); | |
| setEditedImage(null); | |
| } | |
| }; | |
| const handleEdit = useCallback(async () => { | |
| if (!file || !prompt) { | |
| setError('Please upload an image and provide an edit prompt.'); | |
| return; | |
| } | |
| setIsLoading(true); | |
| setError(null); | |
| setEditedImage(null); | |
| try { | |
| const base64Image = await fileToBase64(file); | |
| const response = await editImage(prompt, base64Image, file.type); | |
| const newImagePart = response.candidates?.[0]?.content?.parts?.find(p => p.inlineData); | |
| if (newImagePart && newImagePart.inlineData) { | |
| const newImageData = newImagePart.inlineData; | |
| setEditedImage(`data:${newImageData.mimeType};base64,${newImageData.data}`); | |
| } else { | |
| throw new Error("The model did not return an edited image."); | |
| } | |
| } catch (e: any) { | |
| setError(e.message || 'An error occurred while editing the image.'); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }, [file, prompt]); | |
| return ( | |
| <div className="flex flex-col h-full w-full max-w-6xl mx-auto"> | |
| <h2 className="text-2xl font-bold text-cyan-300 mb-4 text-center">AI Image Editor</h2> | |
| <div className="flex flex-col md:flex-row gap-4 mb-4"> | |
| <div className="flex-1"> | |
| <label htmlFor="image-upload" className="block text-sm font-medium text-gray-300 mb-2">1. Upload Image</label> | |
| <input | |
| id="image-upload" | |
| type="file" | |
| accept="image/*" | |
| onChange={handleFileChange} | |
| className="block w-full text-sm text-gray-400 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-cyan-800 file:text-cyan-100 hover:file:bg-cyan-700" | |
| /> | |
| </div> | |
| <div className="flex-1"> | |
| <label htmlFor="prompt" className="block text-sm font-medium text-gray-300 mb-2">2. Describe Your Edit</label> | |
| <input | |
| id="prompt" | |
| type="text" | |
| value={prompt} | |
| onChange={(e) => setPrompt(e.target.value)} | |
| className="w-full bg-gray-700 border border-gray-600 rounded-md p-2.5 focus:outline-none focus:ring-2 focus:ring-cyan-500 text-white" | |
| placeholder="e.g., Add a retro filter, or remove the person in the background" | |
| /> | |
| </div> | |
| <div className="self-end"> | |
| <button | |
| onClick={handleEdit} | |
| disabled={isLoading || !file || !prompt} | |
| className="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-3 px-6 rounded-md disabled:bg-gray-500 transition-colors" | |
| > | |
| {isLoading ? 'Editing...' : 'Apply Edit'} | |
| </button> | |
| </div> | |
| </div> | |
| {error && <p className="text-red-400 text-center mb-4">{error}</p>} | |
| <div className="flex-grow grid grid-cols-1 md:grid-cols-2 gap-6 mt-4"> | |
| <div className="flex flex-col items-center"> | |
| <h3 className="text-lg font-semibold text-gray-400 mb-2">Original</h3> | |
| <div className="w-full aspect-square bg-gray-800/50 rounded-lg border border-cyan-500/10 flex items-center justify-center overflow-hidden"> | |
| {originalImage ? <img src={originalImage} alt="Original" className="object-contain max-h-full max-w-full" /> : <p className="text-gray-500">Upload an image to start</p>} | |
| </div> | |
| </div> | |
| <div className="flex flex-col items-center"> | |
| <h3 className="text-lg font-semibold text-gray-400 mb-2">Edited</h3> | |
| <div className="w-full aspect-square bg-gray-800/50 rounded-lg border border-cyan-500/10 flex items-center justify-center overflow-hidden"> | |
| {isLoading && <Spinner text="Generating edit..."/>} | |
| {!isLoading && editedImage && <img src={editedImage} alt="Edited" className="object-contain max-h-full max-w-full" />} | |
| {!isLoading && !editedImage && <p className="text-gray-500">Your edited image will appear here</p>} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default ImageEditModule; | |