/** * Room Photo Analyzer using OpenAI GPT-4 Vision * Analyzes room photos to extract dimensions, detect doors/windows, and suggest wardrobe placement */ import OpenAI from 'openai'; // Lazy initialization to avoid errors when API key is not set let openaiClient = null; function getOpenAIClient() { if (!openaiClient) { if (!process.env.OPENAI_API_KEY) { throw new Error('OPENAI_API_KEY is not configured. Add it to server/.env'); } openaiClient = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); } return openaiClient; } /** * Prompt for GPT-4 Vision to analyze room photos */ const ROOM_ANALYSIS_PROMPT = `You are a professional interior designer and architect specializing in custom wardrobe installations. Analyze this room photo with a STRONG FOCUS on the visible wall surfaces (parois) and ceiling (plafond). PRIORITY: Carefully examine every visible wall and the ceiling. Detect their exact colors, materials, and textures as they appear in the photo. This is critical for accurate 3D room reconstruction. Return a JSON object with the following structure: { "roomDimensions": { "widthM": , "lengthM": , "heightM": }, "walls": [ { "side": "back" | "left" | "right", "visible": , "color": "", "material": "paint" | "wallpaper" | "brick" | "wood_panel" | "concrete" | "plaster" | "stone" | "tile", "texture": "smooth" | "textured" | "patterned" | "rough" | "glossy", "hasWindow": , "hasDoor": , "surfaceArea": } ], "windows": [ { "wall": "back" | "left" | "right", "xPercent": , "yPercent": , "widthPercent": , "heightPercent": , "type": "window" | "french_window" | "skylight" | "bay_window" } ], "doors": [ { "wall": "back" | "left" | "right", "xPercent": , "widthPercent": , "heightPercent": , "type": "door" | "double_door" | "opening" | "closet" | "sliding_door" } ], "floor": { "color": "", "material": "wood" | "tile" | "carpet" | "concrete" | "laminate" | "vinyl" | "parquet" }, "ceiling": { "visible": , "color": "", "type": "flat" | "sloped" | "beamed" | "coffered" | "vaulted", "material": "paint" | "plaster" | "wood" | "concrete", "slopeAngleDeg": , "slopeDirection": "front_to_back" | "left_to_right" | null }, "lighting": { "natural": "bright" | "moderate" | "dim", "windowSide": "left" | "right" | "back" | "front" | null }, "suggestedWardrobeWall": "back" | "left" | "right", "suggestedWardrobePosition": { "xPercent": , "widthPercent": , "reason": "" }, "roomType": "bedroom" | "living_room" | "hallway" | "dressing" | "other", "confidence": } IMPORTANT GUIDELINES FOR WALL AND CEILING DETECTION: - ALWAYS include all three walls (back, left, right). Set visible=false only if a wall is truly not visible at all. - Sample the EXACT hex color from the dominant area of each wall surface — do NOT default to #f5f5f0. - Pay close attention to paint color nuances: beige vs cream vs off-white vs warm grey etc. - Detect the wall material: painted plaster, wallpaper, exposed brick, wood paneling, etc. - For the ceiling: detect its actual color (it may not be pure white), its type, and material. - If the ceiling is sloped (mansarde/sous-pente), estimate the slope angle and direction. - Estimate dimensions based on standard furniture sizes if visible (beds, doors, windows). - Standard door height is ~2.1m, standard door width is ~0.8-0.9m. - Standard ceiling height is 2.4-2.7m in residential. - For wardrobe placement, prefer walls without windows/doors and with enough clear space. - Return ONLY valid JSON, no additional text.`; /** * Analyze a room photo using GPT-4 Vision * @param {string} imageBase64 - Base64 encoded image data * @param {string} mimeType - Image MIME type (image/jpeg, image/png, etc.) * @returns {Object} Room analysis result */ export async function analyzeRoomPhoto(imageBase64, mimeType = 'image/jpeg') { const openai = getOpenAIClient(); try { const response = await openai.chat.completions.create({ model: 'gpt-4o', messages: [ { role: 'user', content: [ { type: 'text', text: ROOM_ANALYSIS_PROMPT }, { type: 'image_url', image_url: { url: `data:${mimeType};base64,${imageBase64}`, detail: 'high' } } ] } ], max_tokens: 2000, response_format: { type: 'json_object' } }); const content = response.choices[0]?.message?.content; if (!content) { throw new Error('No response from OpenAI'); } const analysis = JSON.parse(content); // Validate required fields if (!analysis.roomDimensions || !analysis.suggestedWardrobeWall) { throw new Error('Incomplete room analysis'); } return { success: true, analysis, tokensUsed: response.usage?.total_tokens || 0 }; } catch (error) { console.error('Room analysis error:', error); if (error.code === 'invalid_api_key') { throw new Error('Invalid OpenAI API key'); } if (error.code === 'rate_limit_exceeded') { throw new Error('API rate limit exceeded. Please try again later.'); } throw error; } } /** * Convert analysis to 3D room configuration for Three.js * @param {Object} analysis - Room analysis from GPT-4 Vision * @returns {Object} 3D room configuration */ export function analysisTo3DConfig(analysis) { const { roomDimensions, walls, windows, doors, floor, ceiling, suggestedWardrobeWall, suggestedWardrobePosition } = analysis; return { // Room dimensions in meters width: roomDimensions.widthM || 4, length: roomDimensions.lengthM || 4, height: roomDimensions.heightM || 2.5, // Wall configurations with material info walls: { back: (() => { const w = walls?.find(w => w.side === 'back'); return w ? { color: w.color || '#f5f5f0', visible: w.visible !== false, material: w.material || 'paint', texture: w.texture || 'smooth' } : { color: '#f5f5f0', visible: true, material: 'paint', texture: 'smooth' }; })(), left: (() => { const w = walls?.find(w => w.side === 'left'); return w ? { color: w.color || '#f5f5f0', visible: w.visible !== false, material: w.material || 'paint', texture: w.texture || 'smooth' } : { color: '#f5f5f0', visible: true, material: 'paint', texture: 'smooth' }; })(), right: (() => { const w = walls?.find(w => w.side === 'right'); return w ? { color: w.color || '#f5f5f0', visible: w.visible !== false, material: w.material || 'paint', texture: w.texture || 'smooth' } : { color: '#f5f5f0', visible: true, material: 'paint', texture: 'smooth' }; })() }, // Windows for each wall windows: (windows || []).map(w => ({ wall: w.wall, x: (w.xPercent || 50) / 100, y: (w.yPercent || 30) / 100, width: (w.widthPercent || 25) / 100, height: (w.heightPercent || 40) / 100, type: w.type || 'window' })), // Doors for each wall doors: (doors || []).map(d => ({ wall: d.wall, x: (d.xPercent || 10) / 100, width: (d.widthPercent || 20) / 100, height: (d.heightPercent || 85) / 100, type: d.type || 'door' })), // Floor and ceiling floor: { color: floor?.color || '#c4a574', material: floor?.material || 'wood' }, ceiling: { color: ceiling?.color || '#ffffff', type: ceiling?.type || 'flat', visible: ceiling?.visible !== false, material: ceiling?.material || 'paint', slopeAngleDeg: ceiling?.slopeAngleDeg || 0, slopeDirection: ceiling?.slopeDirection || null }, // Wardrobe placement wardrobePlacement: { wall: suggestedWardrobeWall || 'back', xPercent: suggestedWardrobePosition?.xPercent || 25, widthPercent: suggestedWardrobePosition?.widthPercent || 50 } }; } export default { analyzeRoomPhoto, analysisTo3DConfig };