const { useState, useEffect, useRef } = React;
// --- Configuración ---
const API_URL = 'https://tryon.likemedia.ai/api_proxy.php'; // Apunta al backend principal
const STORAGE_KEY = 'lm_tryon_history_v3';
const MOCK_MODE = false; // Set true para probar sin backend
// --- Componentes UI ---
const Header = () => (
Probador Virtual IA
Sube tus fotos y visualiza el resultado en segundos
);
const Dropzone = ({ label, icon, onFile, preview }) => {
const inputRef = useRef(null);
const handleFile = (e) => {
const file = e.target.files[0];
if (file) onFile(file);
};
return (
{label}
inputRef.current.click()}
className={`
border-2 border-dashed rounded-xl h-48 flex flex-col items-center justify-center cursor-pointer transition-all duration-200 relative overflow-hidden group
${preview ? 'border-indigo-500 bg-indigo-50' : 'border-slate-300 hover:border-indigo-400 hover:bg-slate-50 bg-white'}
`}
>
{preview ? (
})
) : (
)}
);
};
const RatioSelect = ({ value, onChange }) => (
);
const ResultCard = ({ item, onDelete }) => {
const [isHovered, setIsHovered] = useState(false);
// Fallback híbrido de descarga (Logic Copied from V1.9)
const handleDownload = async () => {
const url = item.url || item.urlimagen;
const filename = `tryon_${item.id}.png`;
try {
const response = await fetch(url);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
} catch (e) {
console.warn("Direct download failed, using proxy", e);
const downloadBase = API_URL.substring(0, API_URL.lastIndexOf('/'));
window.location.href = `${downloadBase}/download_image.php?url=${encodeURIComponent(url)}`;
}
};
return (
setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>

{/* Overlay */}
{new Date(item.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
);
};
// --- Main App ---
const App = () => {
// State
const [personFile, setPersonFile] = useState(null);
const [clothFile, setClothFile] = useState(null);
const [ratio, setRatio] = useState('1:1');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [history, setHistory] = useState([]);
// Load History on Mount
useEffect(() => {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
try {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed)) setHistory(parsed);
} catch (e) { console.error("History load error", e); }
}
}, []);
// Save History on Change
useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
}, [history]);
// --- B2B Contextual Loader ---
const [isContextMode, setIsContextMode] = useState(false);
useEffect(() => {
const container = document.getElementById('tryon-widget-container-react');
const contextUrl = container?.getAttribute('data-product-image');
if (contextUrl) {
setIsContextMode(true);
setLoading(true); // Show loading while fetching context image
// Fetch the image and convert to File to mimic user upload
fetch(contextUrl)
.then(res => res.blob())
.then(blob => {
const file = new File([blob], "product_context.jpg", { type: blob.type });
setClothFile(file);
// Also define a preview property on the file for the Dropzone to show it (if we were showing it)
// But usually Dropzone takes the file and creates objectURL.
// We can just rely on normal Dropzone behavior or custom UI.
})
.catch(err => console.error("Error loading context product:", err))
.finally(() => setLoading(false));
}
}, []);
const handleClearHistory = () => {
if (confirm('¿Seguro que quieres borrar todo el historial?')) {
setHistory([]);
}
};
const handleDeleteItem = (id) => {
if (confirm('¿Eliminar esta imagen?')) {
setHistory(prev => prev.filter(item => item.id !== id));
}
};
const handleGenerate = async () => {
if (!personFile || !clothFile) {
setError("Por favor sube ambas imágenes");
return;
}
setError(null);
setLoading(true);
const formData = new FormData();
formData.append('action', 'tryon');
formData.append('api_key', 'TEST-KEY-123'); // Debería venir de config
formData.append('user_email', 'react_tester@example.com');
formData.append('prefijo_tabla', 'react_beta');
formData.append('image_ratio', ratio);
formData.append('num_images', '1');
formData.append('imagen_modelo', personFile);
formData.append('imagen_accesorio', clothFile);
try {
if (MOCK_MODE) {
// Simulation
await new Promise(r => setTimeout(r, 2000));
const mockItem = {
id: Date.now().toString(),
url: 'https://via.placeholder.com/600x800',
timestamp: Date.now()
};
setHistory(prev => [mockItem, ...prev]);
} else {
// Real API
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'X-Api-Key': 'TEST-KEY-123' },
body: formData
});
if (!response.ok) throw new Error("Error en el servidor");
const result = await response.json();
const imageUrl = result.url || result.urlimagen || (Array.isArray(result) ? result[0].urlimagen : null);
if (imageUrl) {
const newItem = {
id: Date.now().toString(),
url: imageUrl,
timestamp: Date.now()
};
setHistory(prev => [newItem, ...prev]);
} else {
throw new Error("No se recibió imagen válida");
}
}
} catch (err) {
setError(err.message || "Error desconocido");
} finally {
setLoading(false);
}
};
return (
{/* Context Mode: Show Product Card instead of Dropzone */}
{isContextMode ? (
PRODUCTO DE TIENDA
{clothFile ? (
})
) : (
Cargando producto...
)}
Prenda lista
) : (
)}
{error && (
{error}
)}
{/* Results Section */}
{history.length > 0 && (
Resultados {history.length}
{history.length > 0 && (
)}
{history.map(item => (
))}
)}
);
};
// Render
// Render
const initWidget = () => {
const container = document.getElementById('tryon-widget-container-react') || document.getElementById('root');
if (container) {
const root = ReactDOM.createRoot(container);
root.render();
} else {
console.error("TryOn Widget: Target container not found!");
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWidget);
} else {
initWidget();
}