ttm-webapp-hf / pipeline /progressive_ui.py
daniel-wojahn's picture
Fix chart scaling and component update handling: 1) Improve default scaling of plotly charts with fixed dimensions and better auto-scaling 2) Fix component update mechanism in progressive_ui.py to properly handle different component types
e30d4c0
"""
Progressive UI module for the Tibetan Text Metrics app.
This module provides UI components and helper functions for progressive loading
and updating of metrics as they are computed.
"""
import gradio as gr
import pandas as pd
from typing import Dict, List, Any, Callable
from .progressive_loader import ProgressiveResult, MetricType
from .visualize import generate_visualizations, generate_word_count_chart
import logging
logger = logging.getLogger(__name__)
class ProgressiveUI:
"""
Manages progressive UI updates for the Tibetan Text Metrics app.
This class handles the incremental updates of UI components as metrics
are computed, allowing for a more responsive user experience.
"""
def __init__(self,
metrics_preview: gr.Dataframe,
word_count_plot: gr.Plot,
jaccard_heatmap: gr.Plot,
lcs_heatmap: gr.Plot,
fuzzy_heatmap: gr.Plot,
semantic_heatmap: gr.Plot,
warning_box: gr.Markdown,
progress_container: gr.Row,
heatmap_titles: Dict[str, str],
structural_btn=None):
"""
Initialize the ProgressiveUI.
Args:
metrics_preview: Gradio Dataframe component for metrics preview
word_count_plot: Gradio Plot component for word count visualization
jaccard_heatmap: Gradio Plot component for Jaccard similarity heatmap
lcs_heatmap: Gradio Plot component for LCS similarity heatmap
fuzzy_heatmap: Gradio Plot component for fuzzy similarity heatmap
semantic_heatmap: Gradio Plot component for semantic similarity heatmap
warning_box: Gradio Markdown component for warnings
progress_container: Gradio Row component to hold progress indicators
heatmap_titles: Dictionary mapping metric names to descriptive titles
"""
self.metrics_preview = metrics_preview
self.word_count_plot = word_count_plot
self.jaccard_heatmap = jaccard_heatmap
self.lcs_heatmap = lcs_heatmap
self.fuzzy_heatmap = fuzzy_heatmap
self.semantic_heatmap = semantic_heatmap
self.warning_box = warning_box
self.progress_container = progress_container
self.heatmap_titles = heatmap_titles
self.structural_btn = structural_btn
# Create progress indicators for each metric
with self.progress_container:
self.jaccard_progress = gr.Markdown("πŸ”„ **Jaccard Similarity:** Waiting...", elem_id="jaccard_progress")
self.lcs_progress = gr.Markdown("πŸ”„ **Normalized LCS:** Waiting...", elem_id="lcs_progress")
self.fuzzy_progress = gr.Markdown("πŸ”„ **Fuzzy Similarity:** Waiting...", elem_id="fuzzy_progress")
self.semantic_progress = gr.Markdown("πŸ”„ **Semantic Similarity:** Waiting...", elem_id="semantic_progress")
self.word_count_progress = gr.Markdown("πŸ”„ **Word Counts:** Waiting...", elem_id="word_count_progress")
# Track which components have been updated
self.updated_components = set()
def update(self, result: ProgressiveResult) -> Dict[gr.components.Component, Any]:
"""
Update UI components based on progressive results.
Args:
result: ProgressiveResult object containing the current state of computation
Returns:
Dictionary mapping Gradio components to their updated values
"""
updates = {}
# Always update metrics preview if we have data
if not result.metrics_df.empty:
updates[self.metrics_preview] = result.metrics_df.head(10)
# Update warning if present
if result.warning:
warning_md = f"**⚠️ Warning:** {result.warning}" if result.warning else ""
updates[self.warning_box] = gr.update(value=warning_md, visible=True)
# Generate visualizations for completed metrics
if not result.metrics_df.empty:
# Generate heatmaps for available metrics
heatmaps_data = generate_visualizations(
result.metrics_df, descriptive_titles=self.heatmap_titles
)
# Update heatmaps and progress indicators for completed metrics
for metric_type in result.completed_metrics:
if metric_type == MetricType.JACCARD:
# Update progress indicator
updates[self.jaccard_progress] = "βœ… **Jaccard Similarity:** Complete"
# Update heatmap if not already updated
if self.jaccard_heatmap not in self.updated_components:
if "Jaccard Similarity (%)" in heatmaps_data:
updates[self.jaccard_heatmap] = heatmaps_data["Jaccard Similarity (%)"]
self.updated_components.add(self.jaccard_heatmap)
elif metric_type == MetricType.LCS:
# Update progress indicator
updates[self.lcs_progress] = "βœ… **Normalized LCS:** Complete"
# Update heatmap if not already updated
if self.lcs_heatmap not in self.updated_components:
if "Normalized LCS" in heatmaps_data:
updates[self.lcs_heatmap] = heatmaps_data["Normalized LCS"]
self.updated_components.add(self.lcs_heatmap)
elif metric_type == MetricType.FUZZY:
# Update progress indicator
updates[self.fuzzy_progress] = "βœ… **Fuzzy Similarity:** Complete"
# Update heatmap if not already updated
if self.fuzzy_heatmap not in self.updated_components:
if "Fuzzy Similarity" in heatmaps_data:
updates[self.fuzzy_heatmap] = heatmaps_data["Fuzzy Similarity"]
self.updated_components.add(self.fuzzy_heatmap)
elif metric_type == MetricType.SEMANTIC:
# Update progress indicator
updates[self.semantic_progress] = "βœ… **Semantic Similarity:** Complete"
# Update heatmap if not already updated
if self.semantic_heatmap not in self.updated_components:
if "Semantic Similarity" in heatmaps_data:
updates[self.semantic_heatmap] = heatmaps_data["Semantic Similarity"]
self.updated_components.add(self.semantic_heatmap)
# Generate word count chart if we have data
if not result.word_counts_df.empty:
# Update progress indicator
updates[self.word_count_progress] = "βœ… **Word Counts:** Complete"
# Update chart if not already updated
if self.word_count_plot not in self.updated_components:
updates[self.word_count_plot] = generate_word_count_chart(result.word_counts_df)
self.updated_components.add(self.word_count_plot)
# Update progress indicators for metrics in progress
if not result.is_complete:
# Update progress indicators for metrics that are still in progress
if MetricType.JACCARD not in result.completed_metrics:
updates[self.jaccard_progress] = "⏳ **Jaccard Similarity:** In progress..."
if MetricType.LCS not in result.completed_metrics:
updates[self.lcs_progress] = "⏳ **Normalized LCS:** In progress..."
if MetricType.FUZZY not in result.completed_metrics:
updates[self.fuzzy_progress] = "⏳ **Fuzzy Similarity:** In progress..."
if MetricType.SEMANTIC not in result.completed_metrics:
updates[self.semantic_progress] = "⏳ **Semantic Similarity:** In progress..."
if self.word_count_plot not in self.updated_components:
updates[self.word_count_progress] = "⏳ **Word Counts:** In progress..."
else:
# If computation is complete, enable structural button if available
if self.structural_btn is not None:
updates[self.structural_btn] = gr.update(interactive=True)
logger.info("Enabling structural analysis button via progressive UI")
return updates
def create_progressive_callback(progressive_ui: ProgressiveUI) -> Callable:
"""
Create a callback function for progressive updates.
Args:
progressive_ui: ProgressiveUI instance to handle updates
Returns:
Callback function that can be passed to process_texts
"""
def callback(metrics_df: pd.DataFrame,
word_counts_df: pd.DataFrame,
completed_metrics: List[MetricType],
warning: str,
is_complete: bool) -> None:
"""
Callback function for progressive updates.
Args:
metrics_df: DataFrame with current metrics
word_counts_df: DataFrame with word counts
completed_metrics: List of completed metric types
warning: Warning message
is_complete: Whether computation is complete
"""
result = ProgressiveResult(
metrics_df=metrics_df,
word_counts_df=word_counts_df,
completed_metrics=completed_metrics,
warning=warning,
is_complete=is_complete
)
# Get updates for UI components
updates = progressive_ui.update(result)
# Apply updates to UI components
for component, value in updates.items():
try:
# Handle different component types appropriately
if isinstance(component, gr.Markdown):
# For Markdown components, directly set the value
component.value = value
elif isinstance(value, gr.update):
# For gr.update objects (like button state updates)
component.update(**value.kwargs)
elif isinstance(component, (gr.Plot, gr.Dataframe, gr.HTML, gr.File)):
# For Plot, Dataframe, HTML, and File components, use the update method with the value
if value is not None: # Only update if we have a value
component.update(value=value)
elif hasattr(component, 'update'):
# For other components with update method
component.update(value=value)
else:
logger.warning(f"Cannot update component of type {type(component)}")
except Exception as e:
logger.warning(f"Error updating component: {e}")
return callback