veo2 / index.html
hologramicon's picture
Update index.html
7677b83 verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generador de Imágenes con API</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
.loader {
border: 4px solid #f3f3f3; /* Light grey */
border-top: 4px solid #3498db; /* Blue */
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 10px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto p-4 sm:p-6 md:p-8 max-w-2xl">
<div class="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 mb-6 rounded-md shadow" role="alert">
<p class="font-bold text-lg">Aviso Importante:</p>
<ul class="list-disc list-inside">
<li>Esta interfaz intenta realizar una llamada real a la API de Google para generar imágenes.</li>
<li><strong class="font-semibold">Seguridad:</strong> Introducir tu clave API directamente aquí la expone en el navegador. Para producción, usa un backend.</li>
<li><strong class="font-semibold">Hugging Face Secrets:</strong> Los secretos de HF (como `GEMINI_API`) son para backends. Este HTML estático no puede acceder a ellos directamente. Deberás pegar tu clave en el campo de abajo.</li>
<li>Funcionalidad de video reemplazada por generación de imágenes para demostración.</li>
</ul>
</div>
<div class="bg-white shadow-xl rounded-lg p-6 md:p-8">
<h1 class="text-2xl sm:text-3xl font-bold mb-6 text-center text-gray-800">Generador de Imágenes (con API de Google)</h1>
<div class="mb-5">
<label for="apiKey" class="block text-sm font-medium text-gray-700 mb-1">Tu Clave API de Google AI:</label>
<input type="password" id="apiKey" value="" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="Pega tu clave API aquí (Ej: AIza...)">
<p class="mt-1 text-xs text-gray-500">La clave se usará para llamar a la API de Google. No se almacena permanentemente.</p>
</div>
<div class="mb-5">
<label for="prompt" class="block text-sm font-medium text-gray-700 mb-1">Prompt para la imagen:</label>
<textarea id="prompt" rows="3" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="Ej: Un gato astronauta flotando en el espacio, arte digital."></textarea>
</div>
<button id="generateButton" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2.5 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-150 ease-in-out shadow-md flex items-center justify-center">
<span id="buttonText">Generar Imagen</span>
<div id="buttonLoader" class="loader hidden ml-2" style="width: 20px; height: 20px; border-width: 3px;"></div>
</button>
</div>
<div id="progressContainer" class="my-6 bg-white shadow-xl rounded-lg p-6 md:p-8 hidden">
<p id="progressMessage" class="text-md font-semibold text-gray-700 mb-2 text-center">Generando imagen...</p>
<div class="w-full bg-gray-200 rounded-full h-2.5 overflow-hidden">
<div id="progressBar" class="bg-indigo-500 h-2.5 rounded-full transition-all duration-300 ease-linear" style="width: 0%"></div>
</div>
</div>
<div id="imagePreviewContainer" class="my-6 bg-white shadow-xl rounded-lg p-6 md:p-8 hidden">
<h2 class="text-xl sm:text-2xl font-semibold mb-4 text-center text-gray-800">Vista Previa de la Imagen</h2>
<div class="flex justify-center items-center bg-gray-100 p-2 rounded-md border border-gray-200">
<img id="imageElement" src="#" alt="Imagen generada" class="max-w-full max-h-[400px] h-auto rounded-md object-contain hidden">
</div>
</div>
<div id="messageArea" class="mt-6 p-4 rounded-md text-sm hidden"></div>
</div>
<script>
// --- Elementos del DOM ---
const apiKeyInput = document.getElementById('apiKey');
const promptInput = document.getElementById('prompt');
const generateButton = document.getElementById('generateButton');
const buttonText = document.getElementById('buttonText');
const buttonLoader = document.getElementById('buttonLoader');
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progressBar');
const progressMessage = document.getElementById('progressMessage');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
const imageElement = document.getElementById('imageElement');
const messageArea = document.getElementById('messageArea');
// --- Event Listener para el Botón ---
generateButton.addEventListener('click', async () => {
const apiKey = apiKeyInput.value.trim();
const prompt = promptInput.value.trim();
// --- Limpiar mensajes e imagen previa ---
messageArea.classList.add('hidden');
messageArea.textContent = '';
imagePreviewContainer.classList.add('hidden');
imageElement.classList.add('hidden');
imageElement.src = '#'; // Limpiar imagen anterior
// --- Validación básica ---
if (!apiKey) {
showMessage('Por favor, ingresa tu Clave API de Google AI.', 'error');
return;
}
if (!prompt) {
showMessage('Por favor, ingresa un prompt para la imagen.', 'error');
return;
}
// --- Iniciar UI de Carga ---
generateButton.disabled = true;
buttonText.textContent = 'Generando...';
buttonLoader.classList.remove('hidden');
progressContainer.classList.remove('hidden');
progressBar.style.width = '0%'; // Reset progress bar
progressMessage.textContent = 'Enviando solicitud...';
// Simular un pequeño progreso inicial
setTimeout(() => {
progressBar.style.width = '25%';
progressMessage.textContent = 'Esperando respuesta de la API...';
}, 200);
// --- Lógica de llamada a API de Google (Imagen con imagen-3.0-generate-002) ---
// Documentación de referencia (aunque esta es una API específica):
// https://ai.google.dev/docs/gemini_api_overview
// Este endpoint es para el modelo Imagen (generación de imágenes)
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/imagen-3.0-generate-002:predict?key=${apiKey}`;
const payload = {
instances: [{ "prompt": prompt }],
parameters: { "sampleCount": 1 } // Generar 1 imagen
};
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
progressBar.style.width = '75%';
progressMessage.textContent = 'Procesando respuesta...';
if (!response.ok) {
const errorData = await response.json().catch(() => ({ // Intenta parsear el JSON del error
error: { message: `Error HTTP ${response.status}: ${response.statusText}` }
}));
// El formato del error de la API de Google puede variar.
// Usualmente es { error: { code: ..., message: ..., status: ... } }
const errorMessage = errorData.error?.message || `Error desconocido (${response.status})`;
throw new Error(errorMessage);
}
const result = await response.json();
progressBar.style.width = '100%';
progressMessage.textContent = '¡Imagen recibida!';
if (result.predictions && result.predictions.length > 0 && result.predictions[0].bytesBase64Encoded) {
const imageDataBase64 = result.predictions[0].bytesBase64Encoded;
imageElement.src = `data:image/png;base64,${imageDataBase64}`;
imageElement.classList.remove('hidden');
imagePreviewContainer.classList.remove('hidden');
showMessage('¡Imagen generada exitosamente!', 'success');
} else {
console.error('Respuesta de API inesperada:', result);
throw new Error('No se encontró la imagen en la respuesta de la API.');
}
} catch (error) {
console.error("Error al generar la imagen:", error);
showMessage(`Error: ${error.message}`, 'error');
progressBar.style.width = '100%'; // Marcar como completado aunque haya error
progressBar.classList.remove('bg-indigo-500');
progressBar.classList.add('bg-red-500'); // Cambiar color a rojo en error
progressMessage.textContent = 'Error al generar la imagen.';
} finally {
// --- Restaurar UI ---
// Dejar un pequeño delay para que el usuario vea el mensaje de progreso/error
setTimeout(() => {
generateButton.disabled = false;
buttonText.textContent = 'Generar Imagen';
buttonLoader.classList.add('hidden');
if (!imagePreviewContainer.classList.contains('hidden') && !messageArea.classList.contains('bg-red-100')) {
// Si hay imagen y no hubo error, ocultar barra de progreso
progressContainer.classList.add('hidden');
} else {
// Si hubo error o no hay imagen, dejar la barra de progreso (con el error) visible un poco más
setTimeout(() => progressContainer.classList.add('hidden'), 2000);
}
progressBar.classList.remove('bg-red-500'); // Reset color
progressBar.classList.add('bg-indigo-500');
}, 500);
}
});
// --- Función para Mostrar Mensajes ---
function showMessage(message, type = 'info') {
messageArea.textContent = message;
messageArea.classList.remove('hidden', 'bg-red-100', 'border-red-500', 'text-red-700', 'bg-green-100', 'border-green-500', 'text-green-700', 'bg-blue-100', 'border-blue-500', 'text-blue-700');
messageArea.classList.add('border-l-4', 'p-4'); // Estilos base para el mensaje
if (type === 'error') {
messageArea.classList.add('bg-red-100', 'border-red-500', 'text-red-700');
} else if (type === 'success') {
messageArea.classList.add('bg-green-100', 'border-green-500', 'text-green-700');
} else { // info
messageArea.classList.add('bg-blue-100', 'border-blue-500', 'text-blue-700');
}
}
</script>
</body>
</html>