Spaces:
Paused
Paused
| <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> | |