/** * DesignTip - AI-powered design advice card that fades in below the 3D preview. * Shows contextual tips, compliments, and trends based on current configuration. */ import { useState, useEffect, useRef, useCallback } from 'react'; import styles from './DesignTip.module.css'; const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:3001'; const CATEGORY_ICONS = { compliment: '✨', conseil: '💡', tendance: '🎯' }; export default function DesignTip({ config }) { const [tip, setTip] = useState(null); const [loading, setLoading] = useState(false); const [visible, setVisible] = useState(false); const debounceRef = useRef(null); const abortRef = useRef(null); const fetchTip = useCallback(async (cfg) => { // Cancel previous request if (abortRef.current) abortRef.current.abort(); const controller = new AbortController(); abortRef.current = controller; setLoading(true); try { const body = { config: { width: cfg.totalWidth, height: cfg.height, depth: cfg.depth, installationType: cfg.installationType, columns: cfg.modules?.map(m => ({ type: m.type || m.moduleCode, width: m.width, shelves: m.shelves, drawers: m.drawers, hasRod: m.hasRod })), materialName: cfg.finishLabel || null, doorType: cfg.doorType } }; const res = await fetch(`${API_BASE}/api/ai/design-tips`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal }); if (!res.ok) throw new Error('API error'); const data = await res.json(); if (data.tip) { setTip(data); setVisible(true); } } catch (err) { if (err.name !== 'AbortError') { // Silently fail — tip is optional setVisible(false); } } finally { setLoading(false); } }, []); // Debounced fetch on config change useEffect(() => { if (!config || !config.modules?.length) return; if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { fetchTip(config); }, 2000); // 2s debounce to avoid spamming on rapid changes return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [ config?.totalWidth, config?.height, config?.modules?.length, config?.modules?.map(m => m.type).join(','), config?.finishId, config?.doorType, fetchTip ]); // Cleanup on unmount useEffect(() => { return () => { if (abortRef.current) abortRef.current.abort(); }; }, []); if (!visible && !loading) return null; return (
{loading && !tip && (
)} {tip && (
{CATEGORY_ICONS[tip.category] || '✨'}

{tip.tip}

IA
)}
); }