/** * @license * SPDX-License-Identifier: Apache-2.0 */ /* tslint:disable */ import { ChevronDown, Library, LoaderCircle, Paintbrush, PictureInPicture, Redo2, SendHorizontal, Sparkles, Trash2, Undo2, X, } from 'lucide-react'; import {useEffect, useRef, useState} from 'react'; // This function remains useful for parsing potential error messages function parseError(error: string) { try { // Attempt to parse the error as a JSON object which the proxy might send const errObj = JSON.parse(error); return errObj.message || error; } catch (e) { // If it's not JSON, return the original error string const regex = /{"error":(.*)}/gm; const m = regex.exec(error); try { const e = m[1]; const err = JSON.parse(e); return err.message || error; } catch (e) { return error; } } } export default function Home() { const canvasRef = useRef(null); const fileInputRef = useRef(null); const backgroundImageRef = useRef(null); const dropdownRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); const [prompt, setPrompt] = useState(''); const [generatedImage, setGeneratedImage] = useState(null); const [multiImages, setMultiImages] = useState< {url: string; type: string}[] >([]); const [isLoading, setIsLoading] = useState(false); const [showErrorModal, setShowErrorModal] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [mode, setMode] = useState< 'canvas' | 'editor' | 'imageGen' | 'multi-img-edit' >('editor'); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [apiKey, setApiKey] = useState(''); const [showApiKeyModal, setShowApiKeyModal] = useState(false); // State for canvas history const [history, setHistory] = useState([]); const [historyIndex, setHistoryIndex] = useState(-1); // When switching to canvas mode, initialize it and its history useEffect(() => { if (mode === 'canvas' && canvasRef.current) { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); // If an image already exists from another mode, draw it. if (generatedImage) { const img = new window.Image(); img.onload = () => { ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // Save this as the initial state for this session const dataUrl = canvas.toDataURL(); setHistory([dataUrl]); setHistoryIndex(0); }; img.src = generatedImage; } else { // Otherwise, save the blank state as initial const dataUrl = canvas.toDataURL(); setHistory([dataUrl]); setHistoryIndex(0); } } }, [mode, generatedImage]); // Load background image when generatedImage changes useEffect(() => { if (generatedImage && canvasRef.current) { const img = new window.Image(); img.onload = () => { backgroundImageRef.current = img; drawImageToCanvas(); if (mode === 'canvas') { // A small timeout to let the draw happen before saving setTimeout(saveCanvasState, 50); } }; img.src = generatedImage; } }, [generatedImage, mode]); // Handle clicks outside the dropdown to close it 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); }; }, [dropdownRef]); // Initialize canvas with white background const initializeCanvas = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); }; // Draw the background image to the canvas const drawImageToCanvas = () => { if (!canvasRef.current || !backgroundImageRef.current) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage( backgroundImageRef.current, 0, 0, canvas.width, canvas.height, ); }; // Canvas history functions const saveCanvasState = () => { if (!canvasRef.current) return; const canvas = canvasRef.current; const dataUrl = canvas.toDataURL(); const newHistory = history.slice(0, historyIndex + 1); newHistory.push(dataUrl); setHistory(newHistory); setHistoryIndex(newHistory.length - 1); }; const restoreCanvasState = (index: number) => { if (!canvasRef.current || !history[index]) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const dataUrl = history[index]; const img = new window.Image(); img.onload = () => { 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 < history.length - 1) { const newIndex = historyIndex + 1; setHistoryIndex(newIndex); restoreCanvasState(newIndex); } }; // Get the correct coordinates based on canvas scaling const getCoordinates = (e: any) => { const canvas = canvasRef.current!; const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; return { x: (e.nativeEvent.offsetX || e.nativeEvent.touches?.[0]?.clientX - rect.left) * scaleX, y: (e.nativeEvent.offsetY || e.nativeEvent.touches?.[0]?.clientY - rect.top) * scaleY, }; }; const startDrawing = (e: any) => { const canvas = canvasRef.current!; const ctx = canvas.getContext('2d')!; const {x, y} = getCoordinates(e); if (e.type === 'touchstart') { e.preventDefault(); } ctx.beginPath(); ctx.moveTo(x, y); setIsDrawing(true); }; const draw = (e: any) => { if (!isDrawing) return; if (e.type === 'touchmove') { e.preventDefault(); } const canvas = canvasRef.current!; const ctx = canvas.getContext('2d')!; 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(); }; const handleClear = () => { if (mode === 'canvas' && canvasRef.current) { initializeCanvas(); const dataUrl = canvasRef.current.toDataURL(); setHistory([dataUrl]); setHistoryIndex(0); } setGeneratedImage(null); setMultiImages([]); backgroundImageRef.current = null; setPrompt(''); }; const processFiles = (files: FileList | null) => { if (!files) return; const fileArray = Array.from(files).filter((f) => f.type.startsWith('image/'), ); if (fileArray.length === 0) return; if (!apiKey) { setShowApiKeyModal(true); } if (mode === 'multi-img-edit') { const readers = fileArray.map((file) => { return new Promise<{url: string; type: string}>((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve({url: reader.result as string, type: file.type}); reader.onerror = reject; reader.readAsDataURL(file); }); }); Promise.all(readers).then((newImages) => { setMultiImages((prev) => [...prev, ...newImages]); }); } else { const file = fileArray[0]; const reader = new FileReader(); reader.onload = () => { setGeneratedImage(reader.result as string); }; reader.readAsDataURL(file); } }; const handleFileChange = (e: React.ChangeEvent) => { processFiles(e.target.files); e.target.value = ''; }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); e.currentTarget.classList.remove('border-blue-500'); processFiles(e.dataTransfer.files); }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }; const handleDragEnter = (e: React.DragEvent) => { e.currentTarget.classList.add('border-blue-500'); }; const handleDragLeave = (e: React.DragEvent) => { e.currentTarget.classList.remove('border-blue-500'); }; const removeImage = (indexToRemove: number) => { setMultiImages((prev) => prev.filter((_, index) => index !== indexToRemove), ); }; // *** MODIFIED FUNCTION *** const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!apiKey) { setShowApiKeyModal(true); return; } setIsLoading(true); try { if (mode === 'editor' && !generatedImage) { setErrorMessage('Please upload an image to edit.'); setShowErrorModal(true); return; } if (mode === 'multi-img-edit' && multiImages.length === 0) { setErrorMessage('Please upload at least one image to edit.'); setShowErrorModal(true); return; } const parts: any[] = []; // This logic for building the 'parts' array is correct. if (mode === 'imageGen') { const tempCanvas = document.createElement('canvas'); tempCanvas.width = 960; tempCanvas.height = 540; const tempCtx = tempCanvas.getContext('2d')!; tempCtx.fillStyle = '#FFFFFF'; tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); tempCtx.fillStyle = '#FEFEFE'; tempCtx.fillRect(0, 0, 1, 1); const imageB64 = tempCanvas.toDataURL('image/png').split(',')[1]; parts.push({inlineData: {data: imageB64, mimeType: 'image/png'}}); } else if (mode === 'canvas') { if (!canvasRef.current) return; const canvas = canvasRef.current; const imageB64 = canvas.toDataURL('image/png').split(',')[1]; parts.push({inlineData: {data: imageB64, mimeType: 'image/png'}}); } else if (mode === 'editor' && generatedImage) { const mimeType = generatedImage.substring( generatedImage.indexOf(':') + 1, generatedImage.indexOf(';'), ); const imageB64 = generatedImage.split(',')[1]; parts.push({inlineData: {data: imageB64, mimeType}}); } else if (mode === 'multi-img-edit') { multiImages.forEach((img) => { parts.push({ inlineData: {data: img.url.split(',')[1], mimeType: img.type}, }); }); } parts.push({text: prompt}); // Construct the request body for the Gemini REST API const requestBody = { contents: [{role: 'USER', parts}], }; // Define the proxy endpoint const proxyUrl = `/api-proxy/v1beta/models/gemini-2.5-flash-image-preview:generateContent?key=${apiKey}`; // Use fetch to send the request to your proxy server const response = await fetch(proxyUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), }); if (!response.ok) { const errorData = await response.json(); throw new Error( errorData.error?.message || `HTTP error! status: ${response.status}`, ); } const responseData = await response.json(); // Process the response const result = {message: '', imageData: null}; if (responseData.candidates && responseData.candidates.length > 0) { for (const part of responseData.candidates[0].content.parts) { if (part.text) { result.message = part.text; } else if (part.inlineData) { result.imageData = part.inlineData.data; } } } else { throw new Error('Invalid response structure from API.'); } if (result.imageData) { const imageUrl = `data:image/png;base64,${result.imageData}`; if (mode === 'multi-img-edit') { setGeneratedImage(imageUrl); setMultiImages([]); setMode('editor'); } else { setGeneratedImage(imageUrl); } } else { setErrorMessage( result.message || 'Failed to generate image. Please try again.', ); setShowErrorModal(true); } } catch (error: any) { console.error('Error submitting:', error); setErrorMessage(error.message || 'An unexpected error occurred.'); setShowErrorModal(true); } finally { setIsLoading(false); } }; const closeErrorModal = () => { setShowErrorModal(false); }; const handleApiKeySubmit = (e: React.FormEvent) => { e.preventDefault(); const newApiKey = (e.target as any).apiKey.value; if (newApiKey) { setApiKey(newApiKey); setShowApiKeyModal(false); } }; useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const preventTouchDefault = (e: TouchEvent) => { if (isDrawing) { e.preventDefault(); } }; canvas.addEventListener('touchstart', preventTouchDefault, { passive: false, }); canvas.addEventListener('touchmove', preventTouchDefault, { passive: false, }); return () => { canvas.removeEventListener('touchstart', preventTouchDefault); canvas.removeEventListener('touchmove', preventTouchDefault); }; }, [isDrawing]); const baseDisplayClass = 'w-full sm:h-[60vh] h-[40vh] min-h-[320px] bg-white/90 touch-none flex items-center justify-center p-4 transition-colors'; return ( <>

Nano Banana AIO

constructed with the{' '} gemini api {' '} by{' '} prithivsakthi-ur

{isDropdownOpen && (
)}
{mode === 'canvas' ? (
') 12 12, crosshair", }} />
) : mode === 'editor' ? (
{generatedImage ? ( Current image ) : ( )}
) : mode === 'multi-img-edit' ? (
0 ? 'border-black items-start' : 'border-gray-400' } border-2 border-dashed flex-col`} onDrop={handleDrop} onDragOver={handleDragOver} onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}> {multiImages.length > 0 ? (
{multiImages.map((image, index) => (
{`upload
))}
) : ( )}
) : ( // Image Gen mode display
{generatedImage ? ( Generated image ) : (

Image Generation

Enter a prompt below to create an image

)}
)}
{/* Input form */}
setPrompt(e.target.value)} placeholder={ mode === 'imageGen' ? 'Describe the image you want to create...' : mode === 'multi-img-edit' ? 'Describe how to edit or combine the images...' : 'Add your change...' } className="w-full p-3 sm:p-4 pr-12 sm:pr-14 text-sm sm:text-base border-2 border-black bg-white text-gray-800 shadow-sm focus:ring-2 focus:ring-gray-200 focus:outline-none transition-all" required />
{/* Error Modal */} {showErrorModal && (

Failed to generate

{parseError(errorMessage)}

)} {/* API Key Modal */} {showApiKeyModal && (

Add Gemini API Key

Add the API key to process the request. The API key will be removed if the app page is refreshed or closed.

)}
); }