| | import os |
| | import datetime |
| | import pandas as pd |
| | from collections import Counter |
| | import gradio as gr |
| | import argilla as rg |
| |
|
| | |
| | client = rg.Argilla( |
| | api_url=os.getenv("ARGILLA_API_URL"), api_key=os.getenv("ARGILLA_API_KEY") |
| | ) |
| |
|
| | def fetch_data(dataset_name: str, workspace: str): |
| | """ |
| | Fetch dataset from Argilla. |
| | Args: |
| | dataset_name: Name of the dataset. |
| | workspace: Workspace where the dataset is located. |
| | Returns: |
| | Dataset object or None if an error occurs. |
| | """ |
| | try: |
| | dataset = client.datasets(dataset_name, workspace=workspace) |
| | if not dataset: |
| | raise ValueError(f"Dataset '{dataset_name}' not found in workspace '{workspace}'") |
| | return dataset |
| | except Exception as e: |
| | print(f"Error fetching dataset: {e}") |
| | return None |
| |
|
| |
|
| | def get_progress(dataset) -> dict: |
| | """ |
| | Calculate the annotation progress of the dataset. |
| | Args: |
| | dataset: The dataset to calculate progress for. |
| | Returns: |
| | A dictionary with the total number of records, the number of annotated records, and the progress percentage. |
| | """ |
| | records = list(dataset.records) |
| | total_records = len(records) |
| | annotated_records = len( |
| | [record.status for record in records if record.status == "completed"] |
| | ) |
| | progress = (annotated_records / total_records) * 100 if total_records > 0 else 0 |
| | return { |
| | "total": total_records, |
| | "annotated": annotated_records, |
| | "progress": progress, |
| | } |
| |
|
| |
|
| | def get_leaderboard(dataset) -> dict: |
| | """ |
| | Get the leaderboard of user contributions. |
| | Args: |
| | dataset: The dataset to calculate contributions for. |
| | Returns: |
| | A dictionary with usernames as keys and their contribution counts as values. |
| | """ |
| | contributions = {} |
| | for record in dataset.records: |
| | if record.status == "completed" and record.responses: |
| | responses_list = list(record.responses) |
| | if responses_list: |
| | first_response = responses_list[0] |
| | if hasattr(first_response, "user_id"): |
| | unique_key = record.metadata.get("contribution_id", record.id) |
| | contributions[unique_key] = client.users(id=first_response.user_id).username |
| | return dict(Counter(contributions.values())) |
| |
|
| |
|
| | def update_dashboard(): |
| | """ |
| | Update the dashboard with the latest data. |
| | Returns: |
| | A DataFrame with the top 5 contributors sorted by contribution count. |
| | """ |
| | dataset = fetch_data(os.getenv("DATASET_NAME"), os.getenv("WORKSPACE")) |
| | if not dataset: |
| | return pd.DataFrame(columns=["Usuario", "Contribuciones"]) |
| |
|
| | user_annotations = get_leaderboard(dataset) |
| | leaderboard_df = pd.DataFrame( |
| | list(user_annotations.items()), columns=["Usuario", "Contribuciones"] |
| | ) |
| | leaderboard_df = leaderboard_df.sort_values("Contribuciones", ascending=False).head(5) |
| |
|
| | |
| | leaderboard_df.reset_index(drop=True, inplace=True) |
| | |
| | return leaderboard_df |
| |
|
| |
|
| | |
| | custom_css = """ |
| | @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap'); |
| | |
| | :root { |
| | --primary-blue: #3f8fc7; |
| | --secondary-blue: #2c3e50; |
| | --accent-blue: #1e88e5; |
| | --text-color: #333333; |
| | --border-radius: 8px; |
| | --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
| | --gold: #FFD700; |
| | --silver: #C0C0C0; |
| | --bronze: #cd7f32; |
| | --background: #f5f7f9; |
| | } |
| | |
| | body { |
| | background-color: var(--background); |
| | margin: 0; |
| | padding: 0; |
| | } |
| | |
| | .gradio-container { |
| | font-family: 'JetBrains Mono', monospace; |
| | max-width: 900px; |
| | margin: 0 auto; |
| | } |
| | |
| | .header-container { |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | justify-content: center; |
| | margin-bottom: 20px; |
| | padding: 15px 0; |
| | border-bottom: 2px solid var(--primary-blue); |
| | text-align: center; |
| | } |
| | |
| | .logos-container { |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | flex-wrap: wrap; /* Permite que los logos se coloquen en múltiples líneas */ |
| | gap: 20px; |
| | margin-top: 15px; |
| | padding: 0 10px; /* Añade padding horizontal */ |
| | } |
| | |
| | .logo { |
| | height: auto; /* Cambia la altura fija por altura automática */ |
| | max-height: 150px; /* Establece una altura máxima */ |
| | max-width: 100%; /* Asegura que el logo no exceda el ancho del contenedor */ |
| | width: auto; |
| | } |
| | |
| | h1 { |
| | font-family: 'JetBrains Mono', monospace; |
| | color: var(--secondary-blue); |
| | margin: 0; |
| | padding: 0; |
| | font-size: 28px; |
| | font-weight: 700; |
| | text-align: center; |
| | } |
| | |
| | .dashboard-container { |
| | background-color: white; |
| | border-radius: var(--border-radius); |
| | box-shadow: var(--box-shadow); |
| | padding: 20px; |
| | margin-bottom: 20px; |
| | } |
| | |
| | .project-description { |
| | background-color: white; |
| | border-radius: var(--border-radius); |
| | box-shadow: var(--box-shadow); |
| | padding: 20px; |
| | margin-top: 20px; |
| | line-height: 1.6; |
| | } |
| | |
| | /* Estilos para la tabla */ |
| | .dataframe { |
| | width: 100%; |
| | border-collapse: collapse; |
| | border-radius: var(--border-radius); |
| | overflow: hidden; |
| | box-shadow: var(--box-shadow); |
| | } |
| | |
| | .dataframe th { |
| | background-color: var(--primary-blue); |
| | color: white; |
| | text-align: left; |
| | padding: 12px 15px; |
| | font-weight: 600; |
| | pointer-events: none; /* Deshabilita interacción con encabezados */ |
| | } |
| | |
| | .dataframe td { |
| | padding: 12px 15px; |
| | border-bottom: 1px solid #eee; |
| | } |
| | |
| | /* Estilos para filas con posiciones */ |
| | .dataframe tr:nth-child(1) { |
| | background-color: var(--gold); |
| | font-weight: 600; |
| | } |
| | |
| | .dataframe tr:nth-child(2) { |
| | background-color: var(--silver); |
| | font-weight: 600; |
| | } |
| | |
| | .dataframe tr:nth-child(3) { |
| | background-color: var(--bronze); |
| | font-weight: 600; |
| | } |
| | |
| | .dataframe tr:nth-child(4), |
| | .dataframe tr:nth-child(5) { |
| | background-color: #f8f9fa; |
| | } |
| | |
| | /* Iconos para las posiciones */ |
| | .dataframe tr:nth-child(1) td:first-child::before { |
| | content: "🏆 "; |
| | } |
| | |
| | .dataframe tr:nth-child(2) td:first-child::before { |
| | content: "🥈 "; |
| | } |
| | |
| | .dataframe tr:nth-child(3) td:first-child::before { |
| | content: "🥉 "; |
| | } |
| | |
| | .dataframe tr:nth-child(4) td:first-child::before, |
| | .dataframe tr:nth-child(5) td:first-child::before { |
| | content: "👏 "; |
| | } |
| | |
| | /* Botón de actualización */ |
| | .update-button { |
| | background-color: var(--primary-blue); |
| | color: white; |
| | border: none; |
| | border-radius: var(--border-radius); |
| | padding: 10px 20px; |
| | font-weight: 600; |
| | cursor: pointer; |
| | transition: background-color 0.3s ease; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | margin: 0 auto; |
| | font-family: 'JetBrains Mono', monospace; |
| | } |
| | |
| | .update-button:hover { |
| | background-color: var(--accent-blue); |
| | } |
| | |
| | .update-icon { |
| | margin-right: 8px; |
| | } |
| | |
| | /* Pie de página */ |
| | .footer { |
| | text-align: center; |
| | padding: 15px 0; |
| | color: #666; |
| | font-size: 14px; |
| | border-top: 1px solid #eee; |
| | margin-top: 20px; |
| | } |
| | |
| | /* Deshabilitar solo los iconos de ordenación */ |
| | .dataframe thead tr th svg, |
| | .dataframe thead tr th button { |
| | display: none !important; |
| | pointer-events: none; |
| | } |
| | |
| | .dataframe thead tr th { |
| | pointer-events: none; /* Deshabilita la interacción */ |
| | } |
| | |
| | @media (max-width: 768px) { |
| | .dataframe { |
| | font-size: 12px; /* Reduce el tamaño de la fuente en dispositivos pequeños */ |
| | } |
| | |
| | .dataframe td, .dataframe th { |
| | padding: 8px 10px; /* Reduce el padding */ |
| | } |
| | |
| | /* Ajusta el ancho de las columnas en móvil */ |
| | .dataframe-container .dataframe { |
| | table-layout: fixed; /* Fuerza un diseño de tabla fijo */ |
| | } |
| | } |
| | |
| | @media (max-width: 480px) { |
| | h1 { |
| | font-size: 22px; /* Título más pequeño en dispositivos muy pequeños */ |
| | } |
| | |
| | .project-description h1 { |
| | font-size: 20px; /* Título de descripción más pequeño */ |
| | } |
| | } |
| | |
| | @media (max-width: 768px) { |
| | .gradio-container { |
| | margin: 0 10px; /* Menos margen horizontal en móvil */ |
| | } |
| | |
| | .dashboard-container, .project-description { |
| | padding: 15px 10px; /* Menos padding en móvil */ |
| | } |
| | } |
| | |
| | /* ======= SOPORTE PARA MODO OSCURO ======= */ |
| | @media (prefers-color-scheme: dark) { |
| | :root { |
| | --background: #121212; |
| | --text-color: #ffffff; |
| | --secondary-blue: #5bafec; /* Más brillante para mejorar contraste */ |
| | --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); |
| | } |
| | |
| | body { |
| | background-color: var(--background); |
| | color: var(--text-color); |
| | } |
| | |
| | h1 { |
| | color: white !important; /* Título en blanco como se solicitó */ |
| | } |
| | |
| | .header-container { |
| | border-bottom: 2px solid var(--secondary-blue); |
| | } |
| | |
| | .dashboard-container { |
| | background-color: #1e1e1e; /* Fondo negro para el leaderboard */ |
| | color: white; /* Texto blanco para legibilidad */ |
| | } |
| | |
| | .project-description { |
| | background-color: #1e1e1e; /* Fondo negro para el markdown */ |
| | color: white; /* Texto blanco como se solicitó */ |
| | } |
| | |
| | .project-description h1, |
| | .project-description h2, |
| | .project-description h3, |
| | .project-description strong, |
| | .project-description a { |
| | color: white !important; /* Asegurar que todos los encabezados y enlaces sean blancos */ |
| | } |
| | |
| | .dataframe { |
| | box-shadow: var(--box-shadow); |
| | } |
| | |
| | .dataframe th { |
| | background-color: #3f8fc7; /* Mantener el azul del encabezado */ |
| | } |
| | |
| | .dataframe td { |
| | color: black; /* Texto negro en las celdas como se solicitó */ |
| | border-bottom: 1px solid #444; |
| | } |
| | |
| | /* Override specific row backgrounds for dark mode */ |
| | .dataframe tr:nth-child(1) { |
| | background-color: rgba(255, 215, 0, 0.8); /* Gold */ |
| | color: black; /* Texto negro para contraste con el fondo dorado */ |
| | } |
| | |
| | .dataframe tr:nth-child(2) { |
| | background-color: rgba(192, 192, 192, 0.8); /* Silver */ |
| | color: black; /* Texto negro para contraste con el fondo plateado */ |
| | } |
| | |
| | .dataframe tr:nth-child(3) { |
| | background-color: rgba(205, 127, 50, 0.8); /* Bronze */ |
| | color: black; /* Texto negro para contraste con el fondo bronce */ |
| | } |
| | |
| | .dataframe tr:nth-child(4), |
| | .dataframe tr:nth-child(5) { |
| | background-color: #e0e0e0; |
| | color: black; /* Cambiado de white a black para mejor contraste */ |
| | } |
| | |
| | .footer { |
| | color: #999; /* Color más claro para el pie de página */ |
| | border-top: 1px solid #333; |
| | } |
| | |
| | /* Links en modo oscuro */ |
| | a { |
| | color: #6ebdff !important; |
| | } |
| | |
| | a:hover { |
| | color: #8eccff !important; |
| | } |
| | |
| | /* Ajustes para el botón en modo oscuro */ |
| | .update-button { |
| | background-color: #2a7dc2; |
| | } |
| | |
| | .update-button:hover { |
| | background-color: #3a8dd2; |
| | } |
| | |
| | /* Asegurar que los elementos de markdown tengan texto blanco */ |
| | .project-description p, |
| | .project-description li, |
| | .project-description ul, |
| | .project-description ol { |
| | color: white !important; |
| | } |
| | } |
| | """ |
| |
|
| | |
| | project_description = """ |
| | # Sobre el Proyecto Letras del Carnaval |
| | |
| | El Carnaval de Cádiz es un patrimonio cultural muy importante para la ciudad y, sin embargo, no cuenta con una base de datos estructurada de letras que puedan ser consultadas. Esto dificulta la investigación y el acceso a ese patrimonio cultural por parte de los ciudadanos. |
| | |
| | ## Ayúdanos a hacer el Carnaval de Cádiz más accesible |
| | |
| | Durante el curso 2022-2023 iniciamos el proyecto Letras del Carnaval en conjunto entre el capítulo gaditano de Spain AI y el Curso de Especialización en Inteligencia Artificial y Big Data del IES Rafael Alberti, con el objetivo de buscar una mejor conservación de nuestro patrimonio cultural. |
| | |
| | Este año, alumnado del mismo Curso de Especialización ha formado un grupo de investigación que necesita tu colaboración para abrir una nueva línea que busca hacer el Carnaval más accesible para aquellas personas con problemas de audición. Creemos firmemente que nuestro patrimonio cultural no debería tener límites a la hora de ser disfrutado. |
| | |
| | ## ¿En qué consiste el proyecto? |
| | |
| | Estamos dando los primeros pasos para implementar subtítulos en tiempo real de las letras cantadas en el concurso y en la calle. Lo primero que haremos es establecer un marco de evaluación público de los distintos modelos de transcripción existentes para mejorar su rendimiento en el contexto del Carnaval de Cádiz. |
| | |
| | ## ¿Cómo puedes colaborar? |
| | |
| | Necesitamos encontrar los vídeos en los que aparecen ciertas letras y determinar el segundo preciso en el que comienzan y acaban. Para facilitar este proceso, hemos preparado una aplicación web en la que puedes colaborar. |
| | |
| | **[👉 Accede a la aplicación de colaboración](https://ies-rafael-alberti-colabora-letras-carnaval-cadiz.hf.space/dataset/a1242bf4-0374-4776-9baa-3f52e3135d86/annotation-mode)** |
| | |
| | **[📝 Consulta el tutorial](https://letrascarnavalcadiz.com/colabora-letras-carnaval-cadiz.html)** |
| | """ |
| |
|
| | |
| | with gr.Blocks(css=custom_css) as demo: |
| | with gr.Column(): |
| | |
| | with gr.Row(elem_classes="header-container"): |
| | gr.Markdown("# 🏆 TOP 5 Contribuciones Letras Carnaval Cádiz") |
| | |
| | |
| | with gr.Row(elem_classes="logos-container"): |
| | gr.HTML(""" |
| | <div class="logos-container"> |
| | <picture> |
| | <source srcset="https://i.imgur.com/DzFmXUn.png" media="(prefers-color-scheme: dark)"> |
| | <img src="https://i.imgur.com/uoZoVBk.png" class="logo"> |
| | </picture> |
| | <picture> |
| | <source srcset="https://i.imgur.com/Jm1o4FH.png" media="(prefers-color-scheme: dark)"> |
| | <img src="https://i.imgur.com/DudcB4t.png" alt="Logo IES Rafael Alberti" class="logo"> |
| | </picture> |
| | <picture> |
| | <source srcset="https://i.imgur.com/oZbBU58.png" media="(prefers-color-scheme: dark)"> |
| | <img src="https://i.imgur.com/Wr9uatx.png" alt="Logo CE IA y Big data" class="logo"> |
| | </picture> |
| | </div> |
| | """) |
| | |
| | with gr.Column(elem_classes="dashboard-container"): |
| | |
| | leaderboard_output = gr.Dataframe( |
| | headers=["Usuario", "Contribuciones"], |
| | datatype=["str", "number"], |
| | interactive=False, |
| | elem_classes="dataframe", |
| | column_widths=["50%", "50%"], |
| | ) |
| | |
| | |
| | with gr.Row(): |
| | update_btn = gr.Button("🔄", elem_classes="update-button") |
| | |
| | |
| | with gr.Column(elem_classes="project-description"): |
| | gr.Markdown(project_description) |
| | |
| | |
| | with gr.Row(elem_classes="footer"): |
| | gr.Markdown("© 2025 Letras del Carnaval - Un proyecto del IES Rafael Alberti y Spain AI Cádiz") |
| |
|
| | |
| | demo.load( |
| | update_dashboard, |
| | inputs=None, |
| | outputs=[leaderboard_output], |
| | ) |
| | |
| | |
| | update_btn.click( |
| | update_dashboard, |
| | inputs=None, |
| | outputs=[leaderboard_output], |
| | ) |
| |
|
| |
|
| | if __name__ == "__main__": |
| | demo.launch() |