/** * @license * SPDX-License-Identifier: Apache-2.0 */ /* tslint:disable */ import {GoogleGenAI, Modality} from '@google/genai'; import { ChevronDown, Download, ImageUp, Info, LoaderCircle, Moon, Paintbrush, Redo2, Sparkles, Sun, Trash2, Undo2, X, } from 'lucide-react'; import {useState, useRef, useEffect} from 'react'; // REMOVED: const ai = new GoogleGenAI({apiKey: process.env.API_KEY}); const aspectRatios = ['1:1', '16:9', '9:16', '4:3', '3:4']; type Mode = 'text-to-image' | 'image-to-image' | 'draw-to-image'; type Theme = 'light' | 'dark'; function parseError(error: any): string { if (error instanceof Error) { const match = error.message.match(/"message":\s*"(.*?)"/); if (match && match[1]) { return match[1]; } return error.message; } if (typeof error === 'string') { return error; } return 'An unexpected error occurred.'; } export default function Home() { const [mode, setMode] = useState('text-to-image'); const [prompt, setPrompt] = useState(''); const [sourceImages, setSourceImages] = useState([]); const [resultImages, setResultImages] = useState([]); const [selectedImageIndex, setSelectedImageIndex] = useState(0); const [isLoading, setIsLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [showAdvanced, setShowAdvanced] = useState(true); const [aspectRatio, setAspectRatio] = useState('1:1'); const [downloadType, setDownloadType] = useState<'png' | 'jpeg'>('png'); const [numberOfImages, setNumberOfImages] = useState(1); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [theme, setTheme] = useState('light'); const dropdownRef = useRef(null); const fileInputRef = useRef(null); // New state for API Key management const [apiKey, setApiKey] = useState(''); const [showApiKeyModal, setShowApiKeyModal] = useState(false); const [tempApiKey, setTempApiKey] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); // Canvas state const canvasRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); const [canvasHistory, setCanvasHistory] = useState([]); const [historyIndex, setHistoryIndex] = useState(-1); useEffect(() => { document.documentElement.setAttribute('data-theme', theme); }, [theme]); useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setIsDropdownOpen(false); } } document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const initCanvas = () => { const canvas = canvasRef.current; if (canvas) { const ctx = canvas.getContext('2d'); if (ctx) { ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); const dataUrl = canvas.toDataURL(); setCanvasHistory([dataUrl]); setHistoryIndex(0); } } }; // Initialize canvas when mode changes to draw-to-image useEffect(() => { if (mode === 'draw-to-image') { // A small delay to ensure canvas is in the DOM setTimeout(initCanvas, 50); } }, [mode]); // Load generated image back to canvas in draw mode useEffect(() => { if (mode === 'draw-to-image' && resultImages.length > 0 && canvasRef.current) { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const img = new Image(); img.onload = () => { if (ctx) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); saveCanvasState(); } }; img.src = resultImages[0]; } }, [resultImages]); const toggleTheme = () => { setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); }; const handleModeChange = (newMode: Mode) => { if (mode !== newMode) { setMode(newMode); setResultImages([]); setSelectedImageIndex(0); setErrorMessage(''); setPrompt(''); setSourceImages([]); setNumberOfImages(1); } setIsDropdownOpen(false); }; const handleClear = () => { setPrompt(''); setSourceImages([]); setResultImages([]); setSelectedImageIndex(0); setErrorMessage(''); setShowAdvanced(true); setAspectRatio('1:1'); setDownloadType('png'); setNumberOfImages(1); if (mode === 'draw-to-image') { initCanvas(); } }; // Canvas history functions const saveCanvasState = () => { if (!canvasRef.current) return; const canvas = canvasRef.current; const dataUrl = canvas.toDataURL(); const newHistory = canvasHistory.slice(0, historyIndex + 1); newHistory.push(dataUrl); setCanvasHistory(newHistory); setHistoryIndex(newHistory.length - 1); }; const restoreCanvasState = (index: number) => { if (!canvasRef.current || !canvasHistory[index]) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const dataUrl = canvasHistory[index]; const img = new window.Image(); img.onload = () => { if(ctx) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); } }; img.src = dataUrl; }; const handleUndo = () => { if (historyIndex > 0) { const newIndex = historyIndex - 1; setHistoryIndex(newIndex); restoreCanvasState(newIndex); } }; const handleRedo = () => { if (historyIndex < canvasHistory.length - 1) { const newIndex = historyIndex + 1; setHistoryIndex(newIndex); restoreCanvasState(newIndex); } }; const getCoordinates = (e: React.MouseEvent | React.TouchEvent) => { const canvas = canvasRef.current; if(!canvas) return { x: 0, y: 0 }; const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; let clientX, clientY; if ('touches' in e.nativeEvent) { clientX = e.nativeEvent.touches[0].clientX; clientY = e.nativeEvent.touches[0].clientY; } else { clientX = e.nativeEvent.clientX; clientY = e.nativeEvent.clientY; } return { x: (clientX - rect.left) * scaleX, y: (clientY - rect.top) * scaleY, }; }; const startDrawing = (e: React.MouseEvent | React.TouchEvent) => { if ('touches' in e.nativeEvent) e.preventDefault(); const canvas = canvasRef.current; if(!canvas) return; const ctx = canvas.getContext('2d'); if(!ctx) return; const {x, y} = getCoordinates(e); ctx.beginPath(); ctx.moveTo(x, y); setIsDrawing(true); }; const draw = (e: React.MouseEvent | React.TouchEvent) => { if (!isDrawing) return; if ('touches' in e.nativeEvent) e.preventDefault(); const canvas = canvasRef.current; if(!canvas) return; const ctx = canvas.getContext('2d'); if(!ctx) return; const {x, y} = getCoordinates(e); ctx.lineWidth = 5; ctx.lineCap = 'round'; ctx.strokeStyle = '#000000'; ctx.lineTo(x, y); ctx.stroke(); }; const stopDrawing = () => { if (!isDrawing) return; setIsDrawing(false); saveCanvasState(); }; useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const preventDefault = (e: TouchEvent) => { if (isDrawing) { e.preventDefault(); } }; canvas.addEventListener('touchstart', preventDefault, { passive: false }); canvas.addEventListener('touchmove', preventDefault, { passive: false }); return () => { canvas.removeEventListener('touchstart', preventDefault); canvas.removeEventListener('touchmove', preventDefault); }; }, [isDrawing]); const processFiles = (files: FileList) => { if (!files || files.length === 0) return; const imageFiles = Array.from(files).filter((file) => file.type.startsWith('image/'), ); if (imageFiles.length === 0) return; const readers = imageFiles.map((file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as string); reader.onerror = (error) => reject(error); reader.readAsDataURL(file); }); }); Promise.all(readers) .then((newImages) => { setSourceImages((prev) => [...prev, ...newImages]); setResultImages([]); setSelectedImageIndex(0); }) .catch(() => { setErrorMessage('Failed to read one or more files.'); }); }; const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files) { processFiles(e.target.files); } e.target.value = ''; }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); e.currentTarget.classList.remove('dragover'); if (e.dataTransfer.files) { processFiles(e.dataTransfer.files); } }; const removeImage = (indexToRemove: number) => { setSourceImages((prev) => prev.filter((_, index) => index !== indexToRemove), ); }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }; const handleDragEnter = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); e.currentTarget.classList.add('dragover'); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); e.currentTarget.classList.remove('dragover'); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!apiKey) { setShowApiKeyModal(true); return; } if (isSubmitting) return; setIsSubmitting(true); await generateOrEditImage(); setIsSubmitting(false); } const handleApiKeySubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!tempApiKey) { setErrorMessage("Please enter an API key."); return; } setApiKey(tempApiKey); setShowApiKeyModal(false); // Use a timeout to allow state to update before triggering generation setTimeout(() => { if (isSubmitting) return; setIsSubmitting(true); generateOrEditImage(tempApiKey).finally(() => setIsSubmitting(false)); }, 0); }; const generateOrEditImage = async (currentApiKey?: string) => { const keyToUse = currentApiKey || apiKey; if (!keyToUse) { setShowApiKeyModal(true); return; } if (!prompt) { setErrorMessage('Please enter a prompt to continue.'); return; } if (mode === 'image-to-image' && sourceImages.length === 0) { setErrorMessage('Please upload at least one source image for editing.'); return; } setIsLoading(true); setResultImages([]); setSelectedImageIndex(0); setErrorMessage(''); try { const ai = new GoogleGenAI({apiKey: keyToUse}); if ((mode === 'image-to-image' && sourceImages.length > 0) || mode === 'draw-to-image') { const parts = []; if (mode === 'image-to-image') { const imageParts = sourceImages.map((imgData) => { const mimeType = imgData.substring( imgData.indexOf(':') + 1, imgData.indexOf(';'), ); const imageB64 = imgData.split(',')[1]; return {inlineData: {data: imageB64, mimeType}}; }); parts.push(...imageParts); } else if (mode === 'draw-to-image' && canvasRef.current) { const imageB64 = canvasRef.current.toDataURL('image/png').split(',')[1]; parts.push({inlineData: {data: imageB64, mimeType: 'image/png'}}); } const textPart = {text: prompt}; parts.push(textPart); const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: {parts}, }); let foundImage = false; const responseData = response; if(responseData.candidates && responseData.candidates[0].content.parts) { for (const part of responseData.candidates[0].content.parts) { if (part.inlineData) { const imageUrl = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`; setResultImages([imageUrl]); foundImage = true; break; } } } if (!foundImage) { const textMessage = responseData.text; setErrorMessage( textMessage || 'The model did not return an image. Please try a different prompt.', ); } } else { // Text-to-image const response = await ai.models.generateImages({ model: 'imagen-4.0-fast-generate-001', prompt: prompt, config: { numberOfImages: numberOfImages, aspectRatio: aspectRatio as any, outputMimeType: `image/${downloadType}` as 'image/png' | 'image/jpeg', }, }); if (response.generatedImages && response.generatedImages.length > 0) { const imageUrls = response.generatedImages.map((img) => { const base64Image = img.image.imageBytes; return `data:image/${downloadType};base64,${base64Image}`; }); setResultImages(imageUrls); } else { setErrorMessage( 'The model did not return an image. Please try again.', ); } } } catch (error) { console.error('Error during API call:', error); setErrorMessage(parseError(error)); } finally { setIsLoading(false); } }; const handleDownload = async (imageUrl: string) => { const targetMimeType = `image/${downloadType}`; const targetExtension = downloadType; const sourceMimeType = imageUrl.match(/data:(image\/.*?);/)?.[1]; let finalImageUrl = imageUrl; if (sourceMimeType && sourceMimeType !== targetMimeType) { try { finalImageUrl = await new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); if (!ctx) { reject(new Error('Could not get canvas context')); return; } if (targetMimeType === 'image/jpeg') { ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); } ctx.drawImage(img, 0, 0); resolve(canvas.toDataURL(targetMimeType, 0.9)); }; img.onerror = () => { reject(new Error('Failed to load image for conversion.')); }; img.src = imageUrl; }); } catch (error) { setErrorMessage( error instanceof Error ? error.message : 'Image conversion failed.', ); return; } } const link = document.createElement('a'); link.href = finalImageUrl; link.download = `gemini-studio-image.${targetExtension}`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return (

Gemini-Image-Studio

State-of-the-art image generation and editing app, built by hf.co/prithivMLmods

{/* Input Card */}
INPUT
{isDropdownOpen && (
handleModeChange('text-to-image')} className={`dropdown-item ${ mode === 'text-to-image' ? 'active' : '' }`} role="menuitem"> Text-to-Image
handleModeChange('image-to-image')} className={`dropdown-item ${ mode === 'image-to-image' ? 'active' : '' }`} role="menuitem"> Image-to-Image
handleModeChange('draw-to-image')} className={`dropdown-item ${ mode === 'draw-to-image' ? 'active' : '' }`} role="menuitem"> Draw-to-Image
)}
{mode === 'image-to-image' && (
{sourceImages.length > 0 ? (
{sourceImages.map((image, index) => (
{`Source
))}
) : ( )}
)} {mode === 'draw-to-image' && (
)}
{mode === 'text-to-image' && (
The model used for this mode is imagen-4.0-fast-generate-001.
)} {(mode === 'image-to-image' || mode === 'draw-to-image') && (
The model used for image editing is gemini-2.5-flash-image.
)}