import React, { useState } from "react";
// Componente principal de la aplicación
export default function App() {
const [imageFile, setImageFile] = useState(null);
const [base64ImageData, setBase64ImageData] = useState(null);
const [prompt, setPrompt] = useState("Hacer que el hombre de la imagen se ponga de pie");
const [generatedImage, setGeneratedImage] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// "edit" = editar imagen subida, "generate" = crear desde cero
const [mode, setMode] = useState("edit");
/**
* Maneja el cambio en el input de archivo.
* Convierte la imagen seleccionada a base64.
*/
const handleFileChange = (event) => {
const file = event.target.files[0];
if (file) {
setImageFile(file);
setError(null);
setGeneratedImage(null); // Limpiar imagen generada anteriormente
const reader = new FileReader();
reader.onloadend = () => {
const base64String = reader.result
.replace("data:", "")
.replace(/^.+,/, "");
setBase64ImageData(base64String);
};
reader.onerror = () => {
setError("Error al leer el archivo de imagen.");
};
reader.readAsDataURL(file);
}
};
/**
* Llama a la API de Gemini para generar una imagen.
* Ahora soporta:
* - Modo "edit": prompt + imagen subida
* - Modo "generate": solo prompt (imagen desde cero)
* En ambos casos: estilo Pixar 3D, 4K, formato vertical 9:16
*/
const handleGenerateImage = async () => {
// Validaciones según el modo
if (!prompt) {
setError("Por favor, escribe una instrucción.");
return;
}
if (mode === "edit" && !base64ImageData) {
setError("En modo EDICIÓN debes subir una imagen.");
return;
}
setLoading(true);
setError(null);
setGeneratedImage(null);
// ⚠️ Pon aquí tu API key (idealmente vía variable de entorno)
const apiKey = "";
if (!apiKey) {
setError("Falta la API Key de Gemini. Configúrala en el código o en variables de entorno.");
setLoading(false);
return;
}
// Se recomienda usar el modelo estable gemini-2.5-flash-image
const modelId = "gemini-2.5-flash-image";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:generateContent?key=${apiKey}`;
// Añadimos siempre el estilo Pixar 3D, 4K, vertical
const styleSuffix =
" Estilo Pixar 3D, ultra detallado, calidad 4K, iluminación cinematográfica, formato vertical 9:16 para redes sociales.";
const finalPrompt = `${prompt.trim()}. ${styleSuffix}`;
// Construimos las "parts" según el modo
const parts = [{ text: finalPrompt }];
if (mode === "edit" && base64ImageData && imageFile?.type) {
parts.push({
inlineData: {
mimeType: imageFile.type, // "image/jpeg" o "image/png"
data: base64ImageData,
},
});
}
const payload = {
contents: [
{
parts,
},
],
generationConfig: {
// Solo queremos la imagen de vuelta
responseModalities: ["IMAGE"],
// Forzamos aspecto vertical (9:16)
imageConfig: {
aspectRatio: "9:16",
},
},
};
try {
let response = await fetch(apiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorBody = await response.json().catch(() => ({}));
throw new Error(
`Error de la API: ${response.status} - ${
errorBody.error?.message || "Error desconocido"
}`
);
}
const result = await response.json();
const base64Data =
result?.candidates?.[0]?.content?.parts?.find(
(p) => p.inlineData
)?.inlineData?.data;
if (base64Data) {
const imageUrl = `data:image/png;base64,${base64Data}`;
setGeneratedImage(imageUrl);
} else {
// A veces la respuesta puede ser texto (por ejemplo, si la solicitud no es segura)
const textResponse =
result?.candidates?.[0]?.content?.parts?.[0]?.text;
if (textResponse) {
setError(
`La API devolvió un mensaje de texto en lugar de una imagen: ${textResponse}`
);
} else {
setError(
"No se pudo generar la imagen. La respuesta de la API no contenía datos de imagen."
);
console.error("Respuesta inesperada de la API:", result);
}
}
} catch (err) {
setError(`Error al generar la imagen: ${err.message}`);
console.error(err);
} finally {
setLoading(false);
}
};
return (
Editor de Imagen AI (Pixar 3D · 4K · Vertical)
{/* Selector de modo */}
Modo de trabajo
En ambos modos se genera en estilo Pixar 3D, calidad 4K y formato
vertical 9:16.
{/* Paso 1: Subir Imagen (solo relevante para modo edit) */}
{/* Previsualización de Imagen original */}
{mode === "edit" && imageFile && base64ImageData && !generatedImage && (
Music is composed and performed for many purposes, ranging from aesthetic pleasure, religious or ceremonial purposes, or as an entertainment our product for the marketplace. Music is made for sharing and inspiration. With our new online player it becomes much easier to choose your favorite songs.