""" Gradio app for Hugging Face Spaces: generates a Spanish short story from an idea and adds a text-to-speech narration with joyful, sweet, and expressive storytelling. """ import os import tempfile from typing import Optional, Tuple import gradio as gr from huggingface_hub import InferenceClient from gtts import gTTS MODEL_STORY = "meta-llama/Llama-3.1-8B-Instruct" THEME = gr.themes.Soft() def _gradio_major_version() -> int: """Best-effort parse of the major Gradio version to handle API differences.""" try: return int((gr.__version__ or "").split(".")[0]) except Exception: # noqa: BLE001 return 0 def make_client(model_id: str, provider: Optional[str] = None) -> InferenceClient: """Instantiate an inference client with optional HF token support.""" token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACEHUB_API_TOKEN") kwargs = {"model": model_id, "token": token} if provider: kwargs["provider"] = provider return InferenceClient(**kwargs) story_client = make_client(MODEL_STORY) def _extract_message_content(choice: object) -> str: """Helper to read message content regardless of dict/object shape.""" message = getattr(choice, "message", None) or {} if isinstance(message, dict): return str(message.get("content", "")).strip() return str(getattr(message, "content", "")).strip() def generate_story(idea: str, creativity: float, length_words: int) -> str: """Call Llama to craft a joyful, sweet Spanish story with enthusiastic narration.""" system_prompt = ( "Eres una narradora infantil llena de alegría, dulzura y entusiasmo. " "Tu voz es cálida, animada y contagia felicidad. " "Escribe un cuento mágico y encantador basado en la idea proporcionada. " "Usa un tono ALEGRE, OPTIMISTA y LLENO DE VIDA. " "Incluye muchas exclamaciones de alegría (¡Qué emoción!, ¡Increíble!, ¡Maravilloso!, ¡Fantástico!, ¡Oh, sí!). " "Añade diálogos llenos de energía positiva y sonrisas. " "Usa palabras luminosas y felices: brillante, radiante, risas, sonrisas, cálido, mágico, hermoso. " "Describe los momentos bonitos con entusiasmo desbordante. " "Haz preguntas retóricas emocionadas (¿No es maravilloso?, ¿Verdad que es hermoso?). " "El ritmo debe ser dinámico y alegre, como si estuvieras contando algo emocionante a un niño. " "Incluye un clímax lleno de alegría y esperanza. " "El final debe ser SÚPER FELIZ y reconfortante, que haga sonreír. " "Responde ÚNICAMENTE con el cuento en español, sin introducción ni comentarios adicionales." ) user_prompt = ( f"Idea: {idea}\n" f"Extensión deseada: aproximadamente {length_words} palabras.\n" "¡Escribe ahora un cuento lleno de alegría, dulzura y magia!" ) max_tokens = int(max(200, min(length_words * 3, 2000))) try: response = story_client.chat_completion( messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], temperature=max(0.3, min(creativity, 0.95)), max_tokens=max_tokens, ) return _extract_message_content(response.choices[0]) except Exception as e: return f"Error al generar el cuento: {str(e)}\n\nPor favor, intenta de nuevo o verifica tu token de HF." def generate_audio(text: str) -> Tuple[Optional[str], Optional[str]]: """ Genera audio usando gTTS con acento español y velocidad normal para narración alegre. """ if not text: return None, "No hay texto para narrar." print(f"DEBUG: Generando audio para texto de {len(text)} caracteres...") try: # Crear objeto gTTS con configuración para voz alegre tts = gTTS( text=text, lang='es', # Español tld='com.mx', # Acento mexicano (más cálido y alegre) slow=False # Velocidad normal para mantener energía ) # Guardar en archivo temporal with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp: tts.save(tmp.name) print(f"DEBUG: Audio guardado en {tmp.name}") return tmp.name, None except Exception as e: error_msg = f"Error al generar audio: {str(e)}" print(f"ERROR: {error_msg}") return None, error_msg def run_pipeline(idea: str, creativity: float, length_words: int) -> Tuple[str, str, Optional[str]]: """Main orchestration: story -> audio (with graceful fallback).""" idea_clean = (idea or "").strip() if not idea_clean: return "Por favor ingresa una idea para comenzar.", "⚠️ Falta la idea inicial.", None # Generate story story = generate_story(idea_clean, creativity, int(length_words)) if story.startswith("Error"): return story, "❌ Error al generar el cuento.", None status = "✅ ¡Cuento lleno de alegría creado!" # Generate audio narration audio_path, audio_err = generate_audio(story) if audio_path: status += "\n\n🔊 ¡Narración dulce y alegre lista para escuchar!" else: status += "\n\n⚠️ No se pudo generar la narración de audio." if audio_err: status += f"\nDetalle: {audio_err}" return story, status, audio_path def build_interface() -> gr.Blocks: """Assemble the Gradio Blocks UI with inputs, outputs, and callbacks.""" use_theme_in_blocks = _gradio_major_version() < 6 blocks_kwargs = {"title": "Cuento y Narración IA"} if use_theme_in_blocks: blocks_kwargs["theme"] = THEME with gr.Blocks(**blocks_kwargs) as demo: gr.Markdown( "# 📚✨ Generador de Cuentos Mágicos con Narración Alegre\n" "Convierte una idea en un cuento lleno de alegría, dulzura y magia.\n\n" f"**Modelos utilizados:** Texto: {MODEL_STORY} · Voz: Google TTS (español, voz dulce y alegre)" ) with gr.Row(): idea_box = gr.Textbox( label="💡 Idea base", placeholder="Ejemplo: Un niño encuentra una puerta secreta en el bosque...", lines=3, ) with gr.Row(): creativity_slider = gr.Slider( label="🎨 Creatividad (temperature)", minimum=0.0, maximum=1.0, value=0.75, # Más alta para mayor alegría y expresividad step=0.05, ) length_slider = gr.Slider( label="📏 Longitud aproximada (palabras)", minimum=120, maximum=600, value=280, step=20, ) generate_btn = gr.Button("✨ Crear cuento mágico y alegre", variant="primary") with gr.Row(): with gr.Column(scale=1): story_output = gr.Textbox(label="📖 Cuento mágico generado", lines=16) audio_output = gr.Audio(label="🔊 Narración dulce y alegre", type="filepath") status_box = gr.Markdown(value="¡Esperando tu idea para crear magia! ✨") generate_btn.click( fn=run_pipeline, inputs=[idea_box, creativity_slider, length_slider], outputs=[story_output, status_box, audio_output], ) # Example inputs gr.Examples( examples=[ ["Un robot que aprende a pintar y descubre emociones", 0.78, 280], ["Una niña que puede hablar con las estrellas", 0.75, 250], ["Un gato detective investiga la desaparición de la luna", 0.80, 300], ["Un árbol mágico que concede deseos a quien le canta", 0.76, 260], ], inputs=[idea_box, creativity_slider, length_slider], ) return demo def main() -> None: app = build_interface() launcher = app.queue() if _gradio_major_version() >= 6: launcher.launch(theme=THEME) else: launcher.launch() if __name__ == "__main__": main()