""" Gradio UI para Keyword Spotting API v2.0 Usa el endpoint HTTP /predict para analizar audio y detectar keywords. """ import os import httpx import gradio as gr from dotenv import load_dotenv load_dotenv() # ============================================================================ # CONFIGURACIÓN # ============================================================================ API_URL = os.getenv("API_URL", "http://localhost:8000") API_KEY = os.getenv("API_KEY", "") DEFAULT_KEYWORDS = "sí, no, quizás, imposible, hola, adiós, gracias, por favor" # Autenticación (opcional) GRADIO_USERNAME = os.getenv("GRADIO_USERNAME") GRADIO_PASSWORD = os.getenv("GRADIO_PASSWORD") # ============================================================================ # ESTILOS CSS # ============================================================================ CSS = """ /* Contenedor principal */ .gradio-container { max-width: 900px !important; margin: 0 auto !important; font-family: 'Segoe UI', system-ui, sans-serif !important; } /* Header */ .header-container { text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; margin-bottom: 20px; color: white; } .header-container h1 { margin: 0; font-size: 2em; } .header-container p { margin: 10px 0 0 0; opacity: 0.9; } /* Resultado principal */ .result-box { padding: 24px; border-radius: 12px; background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); color: white; text-align: center; margin: 16px 0; } .result-box.error { background: linear-gradient(135deg, #cb2d3e 0%, #ef473a 100%); } .result-word { font-size: 2.5em; font-weight: bold; margin: 0; text-transform: uppercase; letter-spacing: 2px; } .result-confidence { font-size: 1.2em; margin-top: 8px; opacity: 0.9; } /* Transcripción */ .transcription-box { padding: 16px; background: rgba(102, 126, 234, 0.15); border-left: 4px solid #667eea; border-radius: 0 8px 8px 0; margin: 16px 0; } .transcription-label { font-size: 0.85em; color: #a0a0a0; margin-bottom: 4px; } .transcription-text { font-size: 1.2em; color: #ffffff; font-style: italic; } /* Alternativas */ .alternatives-container { margin-top: 16px; } .alternatives-container > p { color: #ffffff !important; } .alternative-item { display: flex; align-items: center; padding: 12px 16px; background: rgba(255, 255, 255, 0.1); border-radius: 8px; margin-bottom: 8px; border: 1px solid rgba(255, 255, 255, 0.2); } .alternative-keyword { font-weight: 600; min-width: 120px; color: #ffffff; } .alternative-bar { flex: 1; height: 24px; background: #e9ecef; border-radius: 12px; overflow: hidden; margin: 0 12px; } .alternative-fill { height: 100%; background: linear-gradient(90deg, #667eea, #764ba2); border-radius: 12px; transition: width 0.3s ease; } .alternative-score { font-weight: 600; min-width: 60px; text-align: right; color: #a78bfa; } /* Botón */ .primary-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border: none !important; font-size: 1.1em !important; padding: 12px 32px !important; } .primary-btn:hover { opacity: 0.9; transform: translateY(-1px); } """ # ============================================================================ # FUNCIONES # ============================================================================ def format_result_html(result: dict) -> str: """Formatea el resultado en HTML bonito.""" if not result.get("success", False): return f"""
Error
{result.get('message', 'Error desconocido')}
{words_display}
Confianza: {confidence * 100:.1f}%
🔄 Otras palabras detectadas:
' for alt in remaining_alternatives: keyword = alt.get("keyword", "") score = alt.get("score", 0) bar_width = score * 100 html += f"""⚠️
Por favor, graba o sube un audio
⚠️
API_KEY no configurada. Configura la variable de entorno.
🔐
API Key inválida
Error {response.status_code}
{error_detail}
🔌
No se pudo conectar al servidor. ¿Está corriendo la API?
❌
{str(e)}
Detecta palabras clave en audio usando Whisper AI