Smart Land Consulting

Smart Land Consulting | Stop Selling Dirt, Start Selling the Dream
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 (

Room Transformer AI

Circle an object and tell us what you'd like to change it into.

{/* Controls Panel */}

1. Upload Photo

{image && (

2. Describe Change

By clicking, you agree to our privacy policy and to receive a lot analysis via email.

© 2025 Smart Land Consulting LLC. All rights reserved.

Disclaimer: Smart Land Consulting is a design and marketing packaging service. Final home construction costs are subject to site engineering, local permit fees, and specific material selections. We are not a licensed general contractor or lender.

..