Light-Dav's picture
Update app.py
532ce4d verified
import gradio as gr
from transformers import pipeline
import os
import random
# Añade esto para verificar la versión de Gradio en tiempo de ejecución
print(f"Gradio version at runtime: {gr.__version__}")
# --- Model Loading ---
MODEL_ID = "Light-Dav/sentiment-analysis-full-project"
try:
sentiment_analyzer = pipeline("sentiment-analysis", model=MODEL_ID, top_k=None)
model_loaded_successfully = True
print("Sentiment analysis model loaded successfully.")
except Exception as e:
print(f"Error loading model: {e}")
sentiment_analyzer = None
model_loaded_successfully = False
print("Sentiment analysis model failed to load. Please check MODEL_ID and network connection.")
# --- Custom CSS with the NEW COLOR PALETTE and MAXIMUM COMPACTNESS ---
custom_css = """
/* RESETEO BÁSICO Y FONDOS GENERALES */
body {
background-color: #1E2B38; /* Fondo General Oscuro */
color: #FFFFFF; /* Blanco para texto principal */
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 0;
margin: 0;
overflow: hidden; /* Ocultar scrollbar si hay un pequeño desbordamiento */
height: 100vh; /* Asegurar que el body ocupe toda la altura del viewport */
display: flex;
flex-direction: column;
}
/* CONTENEDOR PRINCIPAL DE GRADIO */
.gradio-container {
box-shadow: 0 2px 4px rgba(0, 122, 204, 0.1); /* Sombra más sutil */
border-radius: 6px; /* Borde más pequeño */
overflow: hidden;
background-color: #1E2B38; /* Fondo de la tarjeta, coincide con el body */
padding: 10px; /* Reducir padding general del contenedor */
margin-bottom: 5px; /* Reducir margen inferior */
border: 1px solid #007ACC; /* Borde sutil con Azul Oscuro */
flex-grow: 1; /* Permite que el contenedor ocupe el espacio disponible */
display: flex;
flex-direction: column;
}
/* AJUSTES DE TÍTULOS Y PÁRRAFOS */
h1, h2, h3 {
color: #00BFFF; /* Azul Brillante para títulos */
text-align: center;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
animation: fadeIn 1s ease-in-out;
margin-top: 5px; /* Margen superior muy pequeño */
margin-bottom: 8px; /* Margen inferior reducido */
font-size: 1.4em; /* h1 más pequeño */
}
h2 { font-size: 1.1em; } /* h2 más pequeño */
h3 { font-size: 0.95em; } /* h3 más pequeño, casi texto normal */
p {
color: #AAB7C4; /* Gris medio para texto secundario */
text-align: center;
margin-bottom: 10px; /* Margen debajo de los párrafos reducido */
font-size: 0.8em; /* Tamaño de fuente más pequeño para párrafos */
line-height: 1.3; /* Espaciado entre líneas para legibilidad */
}
/* COMPONENTES DE ENTRADA (TEXTBOX) */
/* Selector para el label del textbox */
.gr-textbox label, .gradio-output .label {
color: #AAB7C4 !important; /* Gris medio para las etiquetas */
font-weight: bold;
font-size: 0.85em; /* Etiqueta más pequeña */
margin-bottom: 3px; /* Espacio mínimo entre etiqueta y caja */
}
/* Selector para el textarea del textbox */
.gr-textbox textarea {
background-color: rgba(0, 122, 204, 0.1); /* Azul Oscuro muy transparente */
border: 1px solid #007ACC; /* Borde con Azul Oscuro */
color: #FFFFFF; /* Texto blanco en el textarea */
border-radius: 5px; /* Bordes más pequeños */
padding: 6px; /* Reducir padding del textarea */
font-size: 0.9em; /* Fuente más pequeña en el textarea */
resize: vertical; /* Permite redimensionar verticalmente */
min-height: 50px; /* Altura mínima */
white-space: pre-wrap; /* Permite saltos de línea y respeta espacios */
word-wrap: break-word; /* Rompe palabras largas */
overflow-wrap: break-word; /* Estándar más moderno */
}
/* BOTONES PRINCIPALES */
/* Cambiado el color del botón primario */
.gr-button.primary {
background-color: #00BFFF !important; /* ¡Azul Brillante para el botón primario (Acento)! */
color: #1E2B38 !important; /* Texto oscuro para el botón primario */
border-radius: 5px;
transition: background-color 0.3s ease;
padding: 7px 12px; /* Padding más pequeño del botón */
font-size: 0.9em; /* Un poco más pequeño para el botón */
font-weight: bold;
margin-top: 8px; /* Reducir margen superior */
margin-bottom: 5px; /* Añadir un pequeño margen inferior */
}
.gr-button.primary:hover {
background-color: #007ACC !important; /* Azul Oscuro al pasar el ratón */
color: #FFFFFF !important; /* Texto blanco al pasar el ratón */
}
/* COMPONENTES DE SALIDA (HTML, LABEL, JSON) */
.gradio-output {
border: 1px solid #4A5B6C; /* Borde sutil con Gris Claro */
border-radius: 5px; /* Borde más pequeño */
padding: 8px; /* Reducir padding de la salida */
margin-top: 8px; /* Reducir margen superior */
background-color: rgba(0, 122, 204, 0.08); /* Fondo más sutil para la salida */
color: #FFFFFF; /* Texto blanco en la salida */
flex-grow: 1; /* Permitir que ocupe el espacio restante */
display: flex;
flex-direction: column;
justify-content: center; /* Centrar verticalmente el contenido */
min-height: 80px; /* Altura mínima para asegurar visibilidad */
width: 100%; /* Asegurar que ocupe todo el ancho disponible */
box-sizing: border-box; /* Incluir padding y borde en el ancho */
}
.gradio-output .label-text {
color: #00BFFF !important; /* Color de acento para el texto del label (LABEL_1, LABEL_0, etc.) */
font-weight: bold;
}
.gradio-output .label-score {
color: #FFFFFF !important; /* Blanco para el score del label */
}
.gradio-output .label-container {
padding-bottom: 5px !important; /* Reducir padding entre elementos del label */
}
/* DISPLAY DE SENTIMIENTO (DENTRO DE HTML OUTPUT) */
.sentiment-display {
text-align: center;
padding: 6px; /* Reducir padding */
border-radius: 4px; /* Borde más pequeño */
margin-top: 3px; /* Margen muy pequeño */
font-size: 1em; /* Un poco más pequeño */
font-weight: bold;
color: #FFFFFF; /* Texto blanco para todos los sentimientos */
/* Asegurar que el texto se envuelva */
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
}
.sentiment-display p { /* Estilo específico para el párrafo de descripción */
font-size: 0.75em; /* Tamaño más pequeño para la descripción */
margin-top: 3px;
margin-bottom: 0;
line-height: 1.2;
white-space: normal; /* Asegurar que el párrafo se envuelva */
word-wrap: break-word;
overflow-wrap: break-word;
}
.sentiment-positive { background-color: #28a745; } /* Mantener estos colores por claridad de sentimiento */
.sentiment-negative { background-color: #dc3545; }
.sentiment-neutral { background-color: #007BFF; }
/* NUEVOS BOTONES DE EJEMPLO DIRECTOS (NO gr.Examples) */
/* Ajustes para el texto de los botones de ejemplo */
.example-button {
background-color: #4A5B6C !important; /* Gris Claro para los botones de ejemplo */
color: #FFFFFF !important;
border: 1px solid #4A5B6C;
border-radius: 3px; /* Borde más pequeño */
padding: 5px 8px; /* Padding más pequeño */
margin: 2px; /* Margen mínimo entre botones */
transition: background-color 0.3s ease;
font-size: 0.7em; /* ¡Tamaño de fuente más pequeño para los ejemplos! */
white-space: normal; /* ¡Permitir que el texto se rompa en varias líneas! */
word-wrap: break-word; /* Rompe palabras largas */
overflow-wrap: break-word; /* Estándar más moderno */
flex-shrink: 1; /* Permitir que se encojan si es necesario */
cursor: pointer; /* Indicar que son clickeables */
flex-grow: 1; /* Permitir que los botones de ejemplo crezcan para llenar el espacio */
min-width: 80px; /* Asegurar un ancho mínimo para cada botón */
text-align: center; /* Centrar el texto dentro del botón */
}
.example-button:hover {
background-color: #007ACC !important; /* Azul Oscuro al pasar el ratón por los ejemplos */
border-color: #00BFFF !important;
}
/* Contenedor de los botones de ejemplo para forzar el "wrap" con flexbox */
.example-buttons-container {
display: flex;
flex-wrap: wrap; /* ¡Esto simula el wrap! */
justify-content: center; /* Centrar los botones si no llenan la línea */
align-items: stretch; /* Asegura que los botones tengan la misma altura si el texto envuelve */
margin-top: 5px; /* Pequeño margen superior */
margin-bottom: 10px; /* Pequeño margen inferior */
}
/* LÍNEAS DIVISORIAS */
hr {
border-top: 1px solid #4A5B6C; /* Línea divisoria con Gris Claro */
margin-top: 10px; /* Reducir margen superior */
margin-bottom: 10px; /* Reducir margen inferior */
}
/* ANIMACIÓN */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* OCULTAR EL PIE DE PÁGINA DE GRADIO (CRUCIAL PARA IFRAMES PEQUEÑOS) */
footer {
opacity: 0; /* Hacerlo invisible */
height: 0 !important; /* Eliminar su altura */
margin: 0 !important; /* Eliminar sus márgenes */
padding: 0 !important; /* Eliminar su padding */
pointer-events: none; /* No interactuable */
visibility: hidden; /* Asegurar que no ocupe espacio visual */
}
.gradio-app > div > :last-child:not(.gradio-container) { /* Apuntar al elemento del pie de página */
display: none !important; /* Otra forma de ocultar completamente si lo anterior no funciona */
}
/* Ajustes para el contenedor de la salida de Label (para que el texto interno envuelva) */
.gradio-output > .label {
white-space: normal !important; /* Permitir que el texto envuelva */
word-wrap: break-word !important; /* Romper palabras largas */
overflow-wrap: break-word !important; /* Estándar moderno */
}
/* Asegurarse de que el texto dentro de los resultados de Gradio (si no es HTML) también envuelva */
.gradio-output > div {
white-space: normal !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
}
/* Ajustes para el texto dentro del gr.Label (Confidence Scores) si se hace visible */
.gr-label-container {
font-size: 0.8em; /* Reducir la fuente de los scores de confianza */
}
.gr-label-container .label-text {
font-size: 1em !important; /* Mantener la etiqueta del score legible */
}
.gr-label-container .label-score {
font-size: 0.9em !important; /* Un poco más pequeño que la etiqueta */
}
"""
# --- Helper Function for Sentiment Interpretation ---
def interpret_sentiment(label, score):
emoji = ""
description = ""
color_class = ""
if label.lower() == "positive" or label.lower() == "label_2":
emoji = "😊"
description = "Positive sentiment detected."
color_class = "sentiment-positive"
elif label.lower() == "negative" or label.lower() == "label_0":
emoji = "😠"
description = "Negative sentiment detected."
color_class = "sentiment-negative"
elif label.lower() == "neutral" or label.lower() == "label_1":
emoji = "😐"
description = "Neutral sentiment detected."
color_class = "sentiment-neutral"
else:
emoji = "❓"
description = "Sentiment not determined."
color_class = ""
# El HTML de salida también debe permitir el salto de línea y reducir la fuente del párrafo
return f"<div class='sentiment-display {color_class}'>{emoji} {label.upper()} ({score:.2f})</div>" + \
f"<p style='color: #FFFFFF; font-size: 0.7em; margin-top: 3px; margin-bottom: 0; line-height: 1.2; white-space: normal; word-wrap: break-word;'>{description}</p>"
# --- Sentiment Analysis Function ---
def analyze_sentiment(text):
if not model_loaded_successfully:
return (
"<div class='sentiment-display'>⚠️ Model Error ⚠️</div><p style='color: #FFFFFF; font-size: 0.7em;'>Model not loaded.</p>",
{},
{"error": "Model loading failed."}
)
if not text.strip():
return (
"<div class='sentiment-display'>✍️ Enter text ✍️</div><p style='color: #FFFFFF; font-size: 0.7em;'>Type text to analyze.</p>",
{},
{"info": "No text entered."}
)
try:
results = sentiment_analyzer(text)[0]
results_sorted = sorted(results, key=lambda x: x['score'], reverse=True)
top_sentiment = results_sorted[0]
label = top_sentiment['label']
score = top_sentiment['score']
confidence_scores_output = {item['label']: item['score'] for item in results}
overall_sentiment_display = interpret_sentiment(label, score)
return (overall_sentiment_display, confidence_scores_output, results)
except Exception as e:
return (
f"<div class='sentiment-display'>❌ Error ❌</div><p style='color: #FFFFFF; font-size: 0.7em;'>Analysis failed.</p>",
{},
{"error_message": str(e)}
)
# --- Lista completa de ejemplos para selección aleatoria ---
ALL_EXAMPLES = [
"The product quality is absolutely outstanding and worth every penny!",
"I found the customer support unhelpful and quite rude, a terrible experience.",
"The new software update introduced several bugs, making it very unstable.",
"This book is a captivating read, I couldn't put it down!",
"The delivery was late, and the package arrived damaged.",
"Despite the bad reviews, I thoroughly enjoyed the film and its unique plot.",
"The instructions were unclear, leading to a lot of confusion during assembly.",
"What a delicious meal! Every dish was prepared to perfection.",
"I'm very disappointed with the recent policy changes; they are unfair.",
"The new art exhibition is thought-provoking and visually stunning.",
"Traffic was unexpectedly heavy, causing significant delays.",
"Overall, a solid performance, though there's room for improvement."
]
# --- Gradio Interface ---
with gr.Blocks(css=custom_css, theme=None) as demo:
gr.Markdown("<h1 style='color: #00BFFF;'>🌌 Sentiment Analyzer 🌌</h1>")
gr.Markdown("<p style='color: #AAB7C4;'>Analyze the emotional tone of your English text.</p>")
with gr.Column(elem_classes="gradio-container"):
text_input = gr.Textbox(
lines=2,
placeholder="Type your English text here...",
label="Your Text",
interactive=True,
value=random.choice(ALL_EXAMPLES) # Establece un ejemplo aleatorio inicial
)
analyze_btn = gr.Button("Analyze Sentiment", variant="primary")
# --- Definir las salidas ANTES de usarlas en los eventos de los botones ---
overall_sentiment_output = gr.HTML(label="Overall Sentiment")
confidence_scores_output = gr.Label(num_top_classes=3, label="Confidence Scores", visible=False)
raw_output = gr.JSON(label="Raw Model Output", visible=False)
# --- Título para los ejemplos ---
gr.Markdown("<h3 style='color: #00BFFF; margin-top: 5px; margin-bottom: 5px;'>Try examples:</h3>")
# Contenedor para los botones de ejemplo para manejar el "wrap" vía CSS
with gr.Row(elem_classes="example-buttons-container"):
# Generar 3 botones de ejemplo aleatorios
for example_text in random.sample(ALL_EXAMPLES, 3):
gr.Button(
example_text,
elem_classes="example-button"
).click(
fn=lambda x: x, # Función simple para pasar el texto del botón al textbox
inputs=[gr.State(example_text)], # Usamos gr.State para pasar el texto del botón
outputs=text_input,
).then(
fn=analyze_sentiment, # Luego de cargar el texto, ejecuta el análisis
inputs=text_input,
outputs=[overall_sentiment_output, confidence_scores_output, raw_output]
)
gr.Markdown("<hr>") # Separador
gr.Markdown("<h2 style='color: #00BFFF;'>📊 Results</h2>")
# Las salidas ya están definidas arriba, ahora solo las colocamos en el layout
# (overall_sentiment_output ya se definió, y su visibilidad y escala se controlan arriba)
# No es necesario re-definirlas aquí, solo asegurarnos de que estén en el flujo de la interfaz
# (Gradio las "conoce" porque ya han sido creadas).
pass # No es necesario poner nada aquí, ya las variables fueron declaradas arriba
# --- Event Listeners ---
analyze_btn.click(
fn=analyze_sentiment,
inputs=text_input,
outputs=[overall_sentiment_output, confidence_scores_output, raw_output]
)
text_input.change(
fn=analyze_sentiment,
inputs=text_input,
outputs=[overall_sentiment_output, confidence_scores_output, raw_output],
# live=True
)
# Launch the Gradio application
demo.launch()