import { useState, useRef, useEffect } from "react"; import { SendHorizontal, LoaderCircle, Trash2, X } from "lucide-react"; import Head from "next/head"; export default function Home() { const canvasRef = useRef(null); const backgroundImageRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); const [penColor, setPenColor] = useState("#000000"); const colorInputRef = useRef(null); const [prompt, setPrompt] = useState(""); const [generatedImage, setGeneratedImage] = useState(null); const [isLoading, setIsLoading] = useState(false); const [showErrorModal, setShowErrorModal] = useState(false); const [errorMessage, setErrorMessage] = useState(""); const [customApiKey, setCustomApiKey] = useState(""); // Load background image when generatedImage changes useEffect(() => { if (generatedImage && canvasRef.current) { // Use the window.Image constructor to avoid conflict with Next.js Image component const img = new window.Image(); img.onload = () => { backgroundImageRef.current = img; drawImageToCanvas(); }; img.src = generatedImage; } }, [generatedImage]); // Initialize canvas with white background when component mounts useEffect(() => { if (canvasRef.current) { initializeCanvas(); } }, []); // Initialize canvas with white background const initializeCanvas = () => { const canvas = canvasRef.current; const ctx = canvas.getContext("2d"); // Fill canvas with white background 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"); // Fill with white background first ctx.fillStyle = "#FFFFFF"; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw the background image ctx.drawImage( backgroundImageRef.current, 0, 0, canvas.width, canvas.height ); }; // Get the correct coordinates based on canvas scaling const getCoordinates = (e) => { const canvas = canvasRef.current; const rect = canvas.getBoundingClientRect(); // Calculate the scaling factor between the internal canvas size and displayed size const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; // Apply the scaling to get accurate coordinates 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) => { const canvas = canvasRef.current; const ctx = canvas.getContext("2d"); const { x, y } = getCoordinates(e); // Prevent default behavior to avoid scrolling on touch devices if (e.type === 'touchstart') { e.preventDefault(); } // Start a new path without clearing the canvas ctx.beginPath(); ctx.moveTo(x, y); setIsDrawing(true); }; const draw = (e) => { if (!isDrawing) return; // Prevent default behavior to avoid scrolling on touch devices 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 = penColor; ctx.lineTo(x, y); ctx.stroke(); }; const stopDrawing = () => { setIsDrawing(false); }; const clearCanvas = () => { const canvas = canvasRef.current; const ctx = canvas.getContext("2d"); // Fill with white instead of just clearing ctx.fillStyle = "#FFFFFF"; ctx.fillRect(0, 0, canvas.width, canvas.height); setGeneratedImage(null); backgroundImageRef.current = null; }; const handleColorChange = (e) => { setPenColor(e.target.value); }; const openColorPicker = () => { if (colorInputRef.current) { colorInputRef.current.click(); } }; const handleKeyDown = (e) => { if (e.key === 'Enter' || e.key === ' ') { openColorPicker(); } }; const handleSubmit = async (e) => { e.preventDefault(); if (!canvasRef.current) return; setIsLoading(true); try { // Get the drawing as base64 data const canvas = canvasRef.current; // Create a temporary canvas to add white background const tempCanvas = document.createElement('canvas'); tempCanvas.width = canvas.width; tempCanvas.height = canvas.height; const tempCtx = tempCanvas.getContext('2d'); // Fill with white background tempCtx.fillStyle = '#FFFFFF'; tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); // Draw the original canvas content on top of the white background tempCtx.drawImage(canvas, 0, 0); const drawingData = tempCanvas.toDataURL("image/png").split(",")[1]; // Create request payload const requestPayload = { prompt, drawingData, customApiKey // Add the custom API key to the payload if it exists }; // Log the request payload (without the full image data for brevity) console.log("Request payload:", { ...requestPayload, drawingData: drawingData ? `${drawingData.substring(0, 50)}... (truncated)` : null, customApiKey: customApiKey ? "**********" : null }); // Send the drawing and prompt to the API const response = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(requestPayload), }); const data = await response.json(); // Log the response (without the full image data for brevity) console.log("Response:", { ...data, imageData: data.imageData ? `${data.imageData.substring(0, 50)}... (truncated)` : null }); if (data.success && data.imageData) { const imageUrl = `data:image/png;base64,${data.imageData}`; setGeneratedImage(imageUrl); } else { console.error("Failed to generate image:", data.error); // Check if the error is related to quota exhaustion or other API errors if (data.error && ( data.error.includes("Resource has been exhausted") || data.error.includes("quota") || response.status === 429 || response.status === 500 )) { setErrorMessage(data.error); setShowErrorModal(true); } else { alert("Failed to generate image. Please try again."); } } } catch (error) { console.error("Error submitting drawing:", error); setErrorMessage(error.message || "An unexpected error occurred."); setShowErrorModal(true); } finally { setIsLoading(false); } }; // Close the error modal const closeErrorModal = () => { setShowErrorModal(false); }; // Handle the custom API key submission const handleApiKeySubmit = (e) => { e.preventDefault(); setShowErrorModal(false); // Will use the customApiKey state in the next API call }; // Add touch event prevention function useEffect(() => { // Function to prevent default touch behavior on canvas const preventTouchDefault = (e) => { if (isDrawing) { e.preventDefault(); } }; // Add event listener when component mounts const canvas = canvasRef.current; if (canvas) { canvas.addEventListener('touchstart', preventTouchDefault, { passive: false }); canvas.addEventListener('touchmove', preventTouchDefault, { passive: false }); } // Remove event listener when component unmounts return () => { if (canvas) { canvas.removeEventListener('touchstart', preventTouchDefault); canvas.removeEventListener('touchmove', preventTouchDefault); } }; }, [isDrawing]); return ( <> Gemini Co-Drawing
{/* Header section with title and tools */}

Gemini Co-Drawing

Built with{" "} Gemini 2.0 native image generation

by{" "} @trudypainter {" "}and{" "} @alexanderchen

{/* Canvas section with notebook paper background */}
{/* Input form that matches canvas width */}
setPrompt(e.target.value)} placeholder="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 font-mono" required />
{/* Error Modal */} {showErrorModal && (

Failed to generate

setCustomApiKey(e.target.value)} placeholder="API Key..." className="w-full p-3 border border-gray-300 rounded mb-4 font-mono text-sm" required />
)}
); }