Spaces:
Runtime error
Runtime error
Update index.html
Browse files- index.html +170 -25
index.html
CHANGED
@@ -304,11 +304,37 @@
|
|
304 |
</head>
|
305 |
<body>
|
306 |
<div class="container">
|
307 |
-
<h1 class="section-title">✨ Creador de Noticias en Video ✨</h1>
|
308 |
|
309 |
-
<!-- Sección
|
310 |
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
311 |
-
<h2 class="text-2xl font-semibold mb-4 text-gray-700">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
312 |
|
313 |
<div class="input-group mb-4">
|
314 |
<label class="block text-gray-700 text-sm font-bold mb-2">Relación de Aspecto del Video:</label>
|
@@ -330,7 +356,7 @@
|
|
330 |
<label for="imageDuration" class="block text-gray-700 text-sm font-bold mb-2">Duración de cada imagen (segundos):</label>
|
331 |
<input type="number" id="imageDuration" value="3" min="1" step="0.5" class="py-2 px-3 rounded-lg">
|
332 |
<div id="imageDurationMessage" class="message-box mt-2"></div>
|
333 |
-
<p class="text-sm text-gray-500 mt-1">Define cuánto tiempo cada imagen aparecerá en el video.
|
334 |
</div>
|
335 |
|
336 |
<div class="input-group mb-4">
|
@@ -343,7 +369,36 @@
|
|
343 |
</div>
|
344 |
</div>
|
345 |
|
346 |
-
<!-- Sección
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
347 |
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
348 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">2. Sube tus Fotos (Máx. 10)</h2>
|
349 |
<div class="input-group mb-4">
|
@@ -357,17 +412,21 @@
|
|
357 |
<div id="imageErrorMessage" class="message-box error-message mt-4 rounded-lg"></div>
|
358 |
</div>
|
359 |
|
360 |
-
<!-- Sección
|
361 |
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
362 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">3. Escribe el Guion de tu Noticia</h2>
|
363 |
<div class="input-group mb-4">
|
364 |
<label for="scriptTextarea" class="block text-gray-700 text-sm font-bold mb-2">Tu guion aquí (un párrafo por cada foto):</label>
|
365 |
<textarea id="scriptTextarea" rows="8" placeholder="Ej: Hola, hoy exploraremos el fascinante mundo de El Principito. En nuestra primera imagen, vemos al Principito en su pequeño asteroide, cuidando su rosa. Esta escena evoca la ternura y la responsabilidad..." class="rounded-lg"></textarea>
|
366 |
</div>
|
|
|
|
|
|
|
|
|
367 |
<div id="llmMessage" class="message-box mt-4 rounded-lg"></div>
|
368 |
</div>
|
369 |
|
370 |
-
<!-- Sección
|
371 |
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
372 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">4. Elige la Voz de tu Narrador</h2>
|
373 |
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
@@ -389,7 +448,7 @@
|
|
389 |
<div id="voiceTestMessage" class="message-box mt-4 rounded-lg"></div>
|
390 |
</div>
|
391 |
|
392 |
-
<!-- Sección
|
393 |
<div class="mb-8 text-center p-4 bg-gray-50 rounded-xl shadow-inner">
|
394 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">5. Generar Video</h2>
|
395 |
<button id="generateVideoBtn" class="btn-primary w-full max-w-xs rounded-xl">Crear Noticia en Video</button>
|
@@ -399,7 +458,7 @@
|
|
399 |
<div id="generateVideoMessage" class="message-box mt-4 rounded-lg"></div>
|
400 |
</div>
|
401 |
|
402 |
-
<!-- Sección
|
403 |
<div class="p-4 bg-gray-50 rounded-xl shadow-inner">
|
404 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">6. Tu Video está Listo</h2>
|
405 |
<div id="videoOutput" class="video-placeholder">
|
@@ -420,20 +479,33 @@
|
|
420 |
</div>
|
421 |
|
422 |
<script>
|
423 |
-
// Referencias a elementos del DOM
|
|
|
|
|
|
|
|
|
424 |
const aspectRatio16_9Radio = document.getElementById('aspectRatio16_9');
|
425 |
const aspectRatio9_16Radio = document.getElementById('aspectRatio9_16');
|
426 |
const imageDurationInput = document.getElementById('imageDuration');
|
427 |
const imageDurationMessage = document.getElementById('imageDurationMessage');
|
428 |
const voiceServiceSelect = document.getElementById('voiceService');
|
429 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
430 |
const photoUpload = document.getElementById('photoUpload');
|
431 |
const thumbnailsContainer = document.getElementById('thumbnails');
|
432 |
const photoCountDisplay = document.getElementById('photoCount');
|
433 |
const imageErrorMessage = document.getElementById('imageErrorMessage');
|
434 |
|
435 |
const scriptTextarea = document.getElementById('scriptTextarea');
|
436 |
-
const
|
|
|
437 |
|
438 |
const voiceLanguageSelect = document.getElementById('voiceLanguage');
|
439 |
const testVoiceBtn = document.getElementById('testVoiceBtn');
|
@@ -519,6 +591,39 @@
|
|
519 |
}
|
520 |
}
|
521 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
522 |
// Validación en tiempo real para la duración de la imagen
|
523 |
imageDurationInput.addEventListener('input', () => {
|
524 |
const value = parseFloat(imageDurationInput.value);
|
@@ -649,51 +754,84 @@
|
|
649 |
// Limpiar mensajes y resetear el área de video
|
650 |
hideMessage(generateVideoMessage);
|
651 |
hideMessage(imageErrorMessage);
|
652 |
-
hideMessage(llmMessage);
|
653 |
videoOutput.innerHTML = '<p>El video generado aparecerá aquí.</p>';
|
654 |
resultVideo.classList.add('hidden');
|
655 |
resultVideo.src = '';
|
656 |
downloadVideoLink.classList.add('hidden');
|
657 |
|
658 |
-
// Recolectar valores de los inputs del usuario
|
|
|
|
|
|
|
659 |
const selectedAspectRatio = document.querySelector('input[name="aspectRatio"]:checked').value;
|
660 |
const imageDuration = parseFloat(imageDurationInput.value);
|
661 |
const selectedVoiceService = voiceServiceSelect.value;
|
662 |
const selectedLanguage = voiceLanguageSelect.value;
|
|
|
|
|
|
|
|
|
663 |
const script = scriptTextarea.value.trim();
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
|
|
|
668 |
return;
|
669 |
}
|
670 |
-
|
671 |
-
|
672 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
673 |
return;
|
674 |
}
|
675 |
-
|
676 |
if (isNaN(imageDuration) || imageDuration <= 0) {
|
677 |
showCustomModal('Duración de Imagen Inválida', 'Por favor, introduce una duración de imagen válida (un número positivo) para cada segmento de video.');
|
678 |
return;
|
679 |
}
|
680 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
681 |
// Mostrar spinner de carga y deshabilitar el botón
|
682 |
loadingSpinner.style.display = 'block';
|
683 |
loadingText.style.display = 'block';
|
684 |
generateVideoBtn.disabled = true;
|
685 |
|
686 |
try {
|
687 |
-
loadingText.textContent = 'Generando tu video:
|
688 |
|
689 |
// Crear objeto FormData para enviar datos y archivos al backend
|
690 |
const formData = new FormData();
|
691 |
formData.append('script', script);
|
|
|
|
|
|
|
692 |
formData.append('aspectRatio', selectedAspectRatio);
|
693 |
formData.append('imageDuration', imageDuration);
|
|
|
|
|
694 |
formData.append('voiceService', selectedVoiceService);
|
695 |
formData.append('voiceLanguage', selectedLanguage);
|
696 |
-
|
|
|
|
|
|
|
|
|
|
|
697 |
// Añadir archivos subidos al FormData
|
698 |
uploadedFiles.forEach((file, index) => {
|
699 |
formData.append(`photos`, file);
|
@@ -739,6 +877,13 @@
|
|
739 |
thumbnailsContainer.innerHTML = 'Arrastra y suelta tus imágenes aquí o usa el botón de "Seleccionar imágenes".';
|
740 |
thumbnailsContainer.classList.remove('has-files');
|
741 |
photoCountDisplay.textContent = '0/10 fotos seleccionadas';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
742 |
|
743 |
} catch (error) {
|
744 |
console.error('Error al generar el vídeo:', error);
|
|
|
304 |
</head>
|
305 |
<body>
|
306 |
<div class="container">
|
307 |
+
<h1 class="section-title">✨ Creador de Noticias en Video con IA ✨</h1>
|
308 |
|
309 |
+
<!-- Sección 0: Configuración de API y Opciones Avanzadas -->
|
310 |
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
311 |
+
<h2 class="text-2xl font-semibold mb-4 text-gray-700">0. Configuración de API y Opciones Avanzadas</h2>
|
312 |
+
|
313 |
+
<div class="input-group mb-4">
|
314 |
+
<label for="llmService" class="block text-gray-700 text-sm font-bold mb-2">Servicio de LLM para Guion:</label>
|
315 |
+
<select id="llmService" class="rounded-lg">
|
316 |
+
<option value="gemini">Google Gemini</option>
|
317 |
+
<option value="gpt">OpenAI GPT</option>
|
318 |
+
<option value="anthropic">Anthropic Claude</option>
|
319 |
+
<option value="bing">Microsoft Bing (Copilot)</option>
|
320 |
+
<option value="custom">Mi LLM Personalizado</option>
|
321 |
+
<option value="none">No usar LLM (solo mi guion)</option>
|
322 |
+
</select>
|
323 |
+
</div>
|
324 |
+
|
325 |
+
<div id="customLlmEndpointGroup" class="input-group mb-4 hidden">
|
326 |
+
<label for="customLlmEndpoint" class="block text-gray-700 text-sm font-bold mb-2">URL del Endpoint de mi LLM Personalizado:</label>
|
327 |
+
<input type="text" id="customLlmEndpoint" placeholder="Ej: https://mi-modelo-personalizado.huggingface.app/generate" class="rounded-lg">
|
328 |
+
<p class="text-sm text-gray-500 mt-1">Tu backend será el encargado de comunicarse con esta URL.</p>
|
329 |
+
</div>
|
330 |
+
|
331 |
+
<div class="input-group mb-4">
|
332 |
+
<label for="genericApiKey" class="block text-gray-700 text-sm font-bold mb-2">Clave de API (Para pruebas locales):</label>
|
333 |
+
<input type="text" id="genericApiKey" placeholder="Ingresa tu clave de API aquí" class="rounded-lg">
|
334 |
+
<div id="apiKeyWarning" class="message-box warning-message mt-2 show">
|
335 |
+
<strong>Advertencia de Seguridad:</strong> Para despliegues (ej. Hugging Face), tu clave de API debe cargarse de forma segura desde variables de entorno/secretos del servidor (ej. `gemini_api`). Si no se introduce aquí, el backend intentará usar las variables de entorno de tu Space.
|
336 |
+
</div>
|
337 |
+
</div>
|
338 |
|
339 |
<div class="input-group mb-4">
|
340 |
<label class="block text-gray-700 text-sm font-bold mb-2">Relación de Aspecto del Video:</label>
|
|
|
356 |
<label for="imageDuration" class="block text-gray-700 text-sm font-bold mb-2">Duración de cada imagen (segundos):</label>
|
357 |
<input type="number" id="imageDuration" value="3" min="1" step="0.5" class="py-2 px-3 rounded-lg">
|
358 |
<div id="imageDurationMessage" class="message-box mt-2"></div>
|
359 |
+
<p class="text-sm text-gray-500 mt-1">Define cuánto tiempo cada imagen aparecerá en el video. Si el guion tiene audio, la duración de la imagen se ajustará a la duración del audio para ese segmento.</p>
|
360 |
</div>
|
361 |
|
362 |
<div class="input-group mb-4">
|
|
|
369 |
</div>
|
370 |
</div>
|
371 |
|
372 |
+
<!-- Sección 1: Generación de Imágenes (si no se suben fotos) -->
|
373 |
+
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
374 |
+
<h2 class="text-2xl font-semibold mb-4 text-gray-700">1. Generación de Imágenes (si no subes fotos)</h2>
|
375 |
+
<div class="flex items-center mb-4">
|
376 |
+
<input type="checkbox" id="generateImagesWithAI" class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 mr-2">
|
377 |
+
<label for="generateImagesWithAI" class="text-gray-700 text-sm">Generar imágenes con IA si no subo fotos</label>
|
378 |
+
</div>
|
379 |
+
<div id="imageGenerationOptions" class="hidden">
|
380 |
+
<div class="input-group mb-4">
|
381 |
+
<label for="imageGenerationService" class="block text-gray-700 text-sm font-bold mb-2">Servicio de Generación de Imágenes:</label>
|
382 |
+
<select id="imageGenerationService" class="rounded-lg">
|
383 |
+
<option value="google_imagen" selected>Google Imagen (Imagen 3.0)</option>
|
384 |
+
<option value="dalle">OpenAI DALL-E (No implementado en backend)</option>
|
385 |
+
<option value="custom_image_gen">Mi Servicio Personalizado (No implementado en backend)</option>
|
386 |
+
</select>
|
387 |
+
</div>
|
388 |
+
<div id="customImageGenEndpointGroup" class="input-group mb-4 hidden">
|
389 |
+
<label for="customImageGenEndpoint" class="block text-gray-700 text-sm font-bold mb-2">URL del Endpoint de mi Servicio Personalizado:</label>
|
390 |
+
<input type="text" id="customImageGenEndpoint" placeholder="Ej: https://mi-generador-ia.huggingface.app/generate" class="rounded-lg">
|
391 |
+
</div>
|
392 |
+
<div class="input-group mb-4">
|
393 |
+
<label for="imageGenerationPrompt" class="block text-gray-700 text-sm font-bold mb-2">Prompt para generar imágenes (ej. "paisajes de El Principito, estilo acuarela"):</label>
|
394 |
+
<textarea id="imageGenerationPrompt" rows="3" placeholder="Describe las imágenes que quieres generar..." class="rounded-lg"></textarea>
|
395 |
+
<p class="text-sm text-gray-500 mt-1">Se generarán entre 1 y 10 imágenes (según tu guion) basadas en este prompt. Est. 1.5 minutos por imagen.</p>
|
396 |
+
</div>
|
397 |
+
</div>
|
398 |
+
<div id="imageGenMessage" class="message-box mt-4 rounded-lg"></div>
|
399 |
+
</div>
|
400 |
+
|
401 |
+
<!-- Sección 2: Carga de Fotos -->
|
402 |
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
403 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">2. Sube tus Fotos (Máx. 10)</h2>
|
404 |
<div class="input-group mb-4">
|
|
|
412 |
<div id="imageErrorMessage" class="message-box error-message mt-4 rounded-lg"></div>
|
413 |
</div>
|
414 |
|
415 |
+
<!-- Sección 3: Escritura del Guion -->
|
416 |
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
417 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">3. Escribe el Guion de tu Noticia</h2>
|
418 |
<div class="input-group mb-4">
|
419 |
<label for="scriptTextarea" class="block text-gray-700 text-sm font-bold mb-2">Tu guion aquí (un párrafo por cada foto):</label>
|
420 |
<textarea id="scriptTextarea" rows="8" placeholder="Ej: Hola, hoy exploraremos el fascinante mundo de El Principito. En nuestra primera imagen, vemos al Principito en su pequeño asteroide, cuidando su rosa. Esta escena evoca la ternura y la responsabilidad..." class="rounded-lg"></textarea>
|
421 |
</div>
|
422 |
+
<div class="flex items-center mb-4">
|
423 |
+
<input type="checkbox" id="useLlmForScript" class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 mr-2">
|
424 |
+
<label for="useLlmForScript" class="text-gray-700 text-sm">Usar LLM para generar/mejorar el guion (basado en fotos y en mi texto)</label>
|
425 |
+
</div>
|
426 |
<div id="llmMessage" class="message-box mt-4 rounded-lg"></div>
|
427 |
</div>
|
428 |
|
429 |
+
<!-- Sección 4: Selección de Voz -->
|
430 |
<div class="mb-8 p-4 bg-gray-50 rounded-xl shadow-inner">
|
431 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">4. Elige la Voz de tu Narrador</h2>
|
432 |
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
|
448 |
<div id="voiceTestMessage" class="message-box mt-4 rounded-lg"></div>
|
449 |
</div>
|
450 |
|
451 |
+
<!-- Sección 5: Generación de Video -->
|
452 |
<div class="mb-8 text-center p-4 bg-gray-50 rounded-xl shadow-inner">
|
453 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">5. Generar Video</h2>
|
454 |
<button id="generateVideoBtn" class="btn-primary w-full max-w-xs rounded-xl">Crear Noticia en Video</button>
|
|
|
458 |
<div id="generateVideoMessage" class="message-box mt-4 rounded-lg"></div>
|
459 |
</div>
|
460 |
|
461 |
+
<!-- Sección 6: Video Resultante -->
|
462 |
<div class="p-4 bg-gray-50 rounded-xl shadow-inner">
|
463 |
<h2 class="text-2xl font-semibold mb-4 text-gray-700">6. Tu Video está Listo</h2>
|
464 |
<div id="videoOutput" class="video-placeholder">
|
|
|
479 |
</div>
|
480 |
|
481 |
<script>
|
482 |
+
// Referencias a elementos del DOM
|
483 |
+
const llmServiceSelect = document.getElementById('llmService');
|
484 |
+
const genericApiKeyInput = document.getElementById('genericApiKey');
|
485 |
+
const customLlmEndpointGroup = document.getElementById('customLlmEndpointGroup');
|
486 |
+
const customLlmEndpointInput = document.getElementById('customLlmEndpoint');
|
487 |
const aspectRatio16_9Radio = document.getElementById('aspectRatio16_9');
|
488 |
const aspectRatio9_16Radio = document.getElementById('aspectRatio9_16');
|
489 |
const imageDurationInput = document.getElementById('imageDuration');
|
490 |
const imageDurationMessage = document.getElementById('imageDurationMessage');
|
491 |
const voiceServiceSelect = document.getElementById('voiceService');
|
492 |
+
|
493 |
+
const generateImagesWithAICheckbox = document.getElementById('generateImagesWithAI');
|
494 |
+
const imageGenerationOptionsDiv = document.getElementById('imageGenerationOptions');
|
495 |
+
const imageGenerationServiceSelect = document.getElementById('imageGenerationService');
|
496 |
+
const customImageGenEndpointGroup = document.getElementById('customImageGenEndpointGroup');
|
497 |
+
const customImageGenEndpointInput = document.getElementById('customImageGenEndpoint');
|
498 |
+
const imageGenerationPromptTextarea = document.getElementById('imageGenerationPrompt');
|
499 |
+
const imageGenMessage = document.getElementById('imageGenMessage');
|
500 |
+
|
501 |
const photoUpload = document.getElementById('photoUpload');
|
502 |
const thumbnailsContainer = document.getElementById('thumbnails');
|
503 |
const photoCountDisplay = document.getElementById('photoCount');
|
504 |
const imageErrorMessage = document.getElementById('imageErrorMessage');
|
505 |
|
506 |
const scriptTextarea = document.getElementById('scriptTextarea');
|
507 |
+
const useLlmForScriptCheckbox = document.getElementById('useLlmForScript');
|
508 |
+
const llmMessage = document.getElementById('llmMessage');
|
509 |
|
510 |
const voiceLanguageSelect = document.getElementById('voiceLanguage');
|
511 |
const testVoiceBtn = document.getElementById('testVoiceBtn');
|
|
|
591 |
}
|
592 |
}
|
593 |
|
594 |
+
// Event listener para mostrar/ocultar el campo de endpoint de LLM personalizado
|
595 |
+
llmServiceSelect.addEventListener('change', () => {
|
596 |
+
if (llmServiceSelect.value === 'custom') {
|
597 |
+
customLlmEndpointGroup.classList.remove('hidden');
|
598 |
+
} else {
|
599 |
+
customLlmEndpointGroup.classList.add('hidden');
|
600 |
+
customLlmEndpointInput.value = '';
|
601 |
+
}
|
602 |
+
});
|
603 |
+
|
604 |
+
// Event listener para mostrar/ocultar las opciones de generación de imagen por IA
|
605 |
+
generateImagesWithAICheckbox.addEventListener('change', () => {
|
606 |
+
if (generateImagesWithAICheckbox.checked) {
|
607 |
+
imageGenerationOptionsDiv.classList.remove('hidden');
|
608 |
+
} else {
|
609 |
+
imageGenerationOptionsDiv.classList.add('hidden');
|
610 |
+
imageGenerationPromptTextarea.value = '';
|
611 |
+
imageGenerationServiceSelect.value = 'google_imagen';
|
612 |
+
customImageGenEndpointGroup.classList.add('hidden');
|
613 |
+
customImageGenEndpointInput.value = '';
|
614 |
+
}
|
615 |
+
});
|
616 |
+
|
617 |
+
// Event listener para mostrar/ocultar el campo de endpoint de generación de imagen personalizado
|
618 |
+
imageGenerationServiceSelect.addEventListener('change', () => {
|
619 |
+
if (imageGenerationServiceSelect.value === 'custom_image_gen') {
|
620 |
+
customImageGenEndpointGroup.classList.remove('hidden');
|
621 |
+
} else {
|
622 |
+
customImageGenEndpointGroup.classList.add('hidden');
|
623 |
+
customImageGenEndpointInput.value = '';
|
624 |
+
}
|
625 |
+
});
|
626 |
+
|
627 |
// Validación en tiempo real para la duración de la imagen
|
628 |
imageDurationInput.addEventListener('input', () => {
|
629 |
const value = parseFloat(imageDurationInput.value);
|
|
|
754 |
// Limpiar mensajes y resetear el área de video
|
755 |
hideMessage(generateVideoMessage);
|
756 |
hideMessage(imageErrorMessage);
|
757 |
+
hideMessage(llmMessage);
|
758 |
videoOutput.innerHTML = '<p>El video generado aparecerá aquí.</p>';
|
759 |
resultVideo.classList.add('hidden');
|
760 |
resultVideo.src = '';
|
761 |
downloadVideoLink.classList.add('hidden');
|
762 |
|
763 |
+
// Recolectar valores de los inputs del usuario
|
764 |
+
const selectedLlmService = llmServiceSelect.value;
|
765 |
+
const genericApiKey = document.getElementById('genericApiKey').value.trim(); // Se recoge por si el backend lo requiere para local
|
766 |
+
const customLlmEndpoint = document.getElementById('customLlmEndpoint').value.trim(); // Se recoge por si el backend lo requiere para custom
|
767 |
const selectedAspectRatio = document.querySelector('input[name="aspectRatio"]:checked').value;
|
768 |
const imageDuration = parseFloat(imageDurationInput.value);
|
769 |
const selectedVoiceService = voiceServiceSelect.value;
|
770 |
const selectedLanguage = voiceLanguageSelect.value;
|
771 |
+
const shouldGenerateImages = generateImagesWithAICheckbox.checked;
|
772 |
+
const imageGenService = document.getElementById('imageGenerationService').value; // Se recoge por si el backend lo requiere
|
773 |
+
const customImageGenEndpoint = document.getElementById('customImageGenEndpoint').value.trim(); // Se recoge por si el backend lo requiere
|
774 |
+
const imageGenPrompt = imageGenerationPromptTextarea.value.trim();
|
775 |
const script = scriptTextarea.value.trim();
|
776 |
+
const useLlmForScript = useLlmForScriptCheckbox.checked;
|
777 |
+
|
778 |
+
// Validaciones de entrada del usuario con modal personalizado
|
779 |
+
if (uploadedFiles.length === 0 && !shouldGenerateImages) {
|
780 |
+
showCustomModal('Faltan Imágenes', 'Por favor, sube al menos una foto O marca la opción "Generar imágenes con IA" y proporciona un prompt.');
|
781 |
return;
|
782 |
}
|
783 |
+
if (uploadedFiles.length > 0 && shouldGenerateImages) {
|
784 |
+
showCustomModal('Conflicto de Imágenes', 'Has subido fotos Y activado la generación de imágenes con IA. Por favor, elige solo una opción.');
|
785 |
+
return;
|
786 |
+
}
|
787 |
+
if (shouldGenerateImages && !imageGenPrompt) {
|
788 |
+
showCustomModal('Prompt de Imagen Requerido', 'Para generar imágenes con IA, debes proporcionar un prompt descriptivo.');
|
789 |
+
return;
|
790 |
+
}
|
791 |
+
if (!script && !useLlmForScript) {
|
792 |
+
showCustomModal('Falta el Guion', 'Por favor, escribe un guion para tu noticia O marca la opción "Usar LLM para generar/mejorar el guion".');
|
793 |
return;
|
794 |
}
|
|
|
795 |
if (isNaN(imageDuration) || imageDuration <= 0) {
|
796 |
showCustomModal('Duración de Imagen Inválida', 'Por favor, introduce una duración de imagen válida (un número positivo) para cada segmento de video.');
|
797 |
return;
|
798 |
}
|
799 |
+
// Aunque el backend Python solo usará Gemini, estas validaciones son para el UI
|
800 |
+
if (useLlmForScript && selectedLlmService === 'custom' && !customLlmEndpoint) {
|
801 |
+
showCustomModal('URL de LLM Personalizado Requerida', 'Por favor, introduce la URL del endpoint de tu LLM Personalizado.');
|
802 |
+
return;
|
803 |
+
}
|
804 |
+
if (shouldGenerateImages && imageGenService === 'custom_image_gen' && !customImageGenEndpoint) {
|
805 |
+
showCustomModal('URL de Servicio Personalizado Requerida', 'Para usar un servicio de generación de imágenes personalizado, debes proporcionar su URL de endpoint.');
|
806 |
+
return;
|
807 |
+
}
|
808 |
+
|
809 |
// Mostrar spinner de carga y deshabilitar el botón
|
810 |
loadingSpinner.style.display = 'block';
|
811 |
loadingText.style.display = 'block';
|
812 |
generateVideoBtn.disabled = true;
|
813 |
|
814 |
try {
|
815 |
+
loadingText.textContent = 'Generando tu video con IA: texto, imágenes, audio y composición... esto puede tardar unos minutos.';
|
816 |
|
817 |
// Crear objeto FormData para enviar datos y archivos al backend
|
818 |
const formData = new FormData();
|
819 |
formData.append('script', script);
|
820 |
+
formData.append('useLlmForScript', useLlmForScript);
|
821 |
+
formData.append('llmService', selectedLlmService);
|
822 |
+
formData.append('customLlmEndpoint', customLlmEndpoint);
|
823 |
formData.append('aspectRatio', selectedAspectRatio);
|
824 |
formData.append('imageDuration', imageDuration);
|
825 |
+
formData.append('shouldGenerateImages', shouldGenerateImages);
|
826 |
+
formData.append('imageGenerationPrompt', imageGenPrompt);
|
827 |
formData.append('voiceService', selectedVoiceService);
|
828 |
formData.append('voiceLanguage', selectedLanguage);
|
829 |
+
// Si el genericApiKey se proporciona en el frontend, se envía.
|
830 |
+
// En despliegues de HF, el backend debe obtenerlo de los secrets.
|
831 |
+
if (genericApiKey) {
|
832 |
+
formData.append('genericApiKey', genericApiKey);
|
833 |
+
}
|
834 |
+
|
835 |
// Añadir archivos subidos al FormData
|
836 |
uploadedFiles.forEach((file, index) => {
|
837 |
formData.append(`photos`, file);
|
|
|
877 |
thumbnailsContainer.innerHTML = 'Arrastra y suelta tus imágenes aquí o usa el botón de "Seleccionar imágenes".';
|
878 |
thumbnailsContainer.classList.remove('has-files');
|
879 |
photoCountDisplay.textContent = '0/10 fotos seleccionadas';
|
880 |
+
imageGenerationPromptTextarea.value = '';
|
881 |
+
generateImagesWithAICheckbox.checked = false;
|
882 |
+
imageGenerationOptionsDiv.classList.add('hidden');
|
883 |
+
llmServiceSelect.value = 'gemini';
|
884 |
+
customLlmEndpointGroup.classList.add('hidden');
|
885 |
+
customLlmEndpointInput.value = '';
|
886 |
+
useLlmForScriptCheckbox.checked = false;
|
887 |
|
888 |
} catch (error) {
|
889 |
console.error('Error al generar el vídeo:', error);
|