import React, { useState, useRef, useEffect } from 'react';
import { Upload, Eraser, Wand2, RotateCcw, Image as ImageIcon, CheckCircle2, AlertCircle, Loader2 } from 'lucide-react';
const apiKey = ""; // API key is handled by the environment
const RoomTransformer = () => {
const [image, setImage] = useState(null);
const [resultImage, setResultImage] = useState(null);
const [prompt, setPrompt] = useState("");
const [isProcessing, setIsProcessing] = useState(false);
const [error, setError] = useState(null);
const [isDrawing, setIsDrawing] = useState(false);
const [brushSize, setBrushSize] = useState(30);
const canvasRef = useRef(null);
const contextRef = useRef(null);
const containerRef = useRef(null);
// Initialize Canvas
useEffect(() => {
if (image && canvasRef.current) {
const canvas = canvasRef.current;
const container = containerRef.current;
// Responsive sizing
const displayWidth = container.clientWidth;
const scale = displayWidth / image.width;
const displayHeight = image.height * scale;
canvas.width = displayWidth;
canvas.height = displayHeight;
const context = canvas.getContext('2d');
context.lineCap = "round";
context.strokeStyle = "rgba(255, 0, 0, 0.5)"; // Semi-transparent red for the "circle/mask"
context.lineWidth = brushSize;
contextRef.current = context;
}
}, [image, brushSize]);
const handleImageUpload = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
setImage(img);
setResultImage(null);
setError(null);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
};
const startDrawing = ({ nativeEvent }) => {
const { offsetX, offsetY } = nativeEvent;
contextRef.current.beginPath();
contextRef.current.moveTo(offsetX, offsetY);
setIsDrawing(true);
};
const draw = ({ nativeEvent }) => {
if (!isDrawing) return;
const { offsetX, offsetY } = nativeEvent;
contextRef.current.lineTo(offsetX, offsetY);
contextRef.current.stroke();
};
const stopDrawing = () => {
contextRef.current.closePath();
setIsDrawing(false);
};
const clearCanvas = () => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
};
const callGeminiAI = async (base64Image, userPrompt) => {
const systemPrompt = "You are an expert interior designer and AI image editor. The user has uploaded a photo of a room and circled/highlighted an object. Change only the highlighted object based on their description. Maintain the lighting, perspective, and overall style of the room.";
// Exponential Backoff helper
const fetchWithRetry = async (url, options, retries = 5, backoff = 1000) => {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} catch (err) {
if (retries > 0) {
await new Promise(resolve => setTimeout(resolve, backoff));
return fetchWithRetry(url, options, retries - 1, backoff * 2);
}
throw err;
}
};
try {
// In a real implementation with a mask, we might combine the original image and the mask
// but for this preview model, we send the prompt and the base image.
const payload = {
contents: [
{
role: "user",
parts: [
{ text: `Modify this photo. Specifically, change the object I've indicated to: ${userPrompt}` },
{ inlineData: { mimeType: "image/png", data: base64Image.split(',')[1] } }
]
}
],
generationConfig: {
responseModalities: ["IMAGE"]
}
};
const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent?key=${apiKey}`;
const result = await fetchWithRetry(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
const generatedBase64 = result.candidates?.[0]?.content?.parts?.find(p => p.inlineData)?.inlineData?.data;
if (generatedBase64) {
setResultImage(`data:image/png;base64,${generatedBase64}`);
} else {
throw new Error("No image was generated. Please try a different prompt.");
}
} catch (err) {
setError(err.message || "Failed to transform the room. Please try again.");
} finally {
setIsProcessing(false);
}
};
const handleTransform = async () => {
if (!image || !prompt) return;
setIsProcessing(true);
setError(null);
// Convert current canvas state (image + drawing) to a single base64
const tempCanvas = document.createElement('canvas');
tempCanvas.width = image.width;
tempCanvas.height = image.height;
const tempCtx = tempCanvas.getContext('2d');
// Draw original image
tempCtx.drawImage(image, 0, 0);
// Overlay the user's drawing (scaled to original size)
const overlayCanvas = canvasRef.current;
tempCtx.globalAlpha = 0.5;
tempCtx.drawImage(overlayCanvas, 0, 0, image.width, image.height);
const combinedBase64 = tempCanvas.toDataURL('image/png');
await callGeminiAI(combinedBase64, prompt);
};
return (
);
};
export default RoomTransformer;
Room Transformer AI
Circle an object and tell us what you'd like to change it into.
{/* Controls Panel */}
{image && (
)}
{/* Canvas/Display Area */}
1. Upload Photo
2. Describe Change
{!image ? (
) : (
)}
{error && (
)}
{resultImage && (
)}
Upload a photo to get started
Step: Draw over the object
Editing Mode
Oops! Something went wrong
{error}